완료버튼누르면 실제 연락처에 추가되게?
저번시간에 유저 허락을 받았으면
이제 연락처 맘대로 꺼내쓸 수 있습니다.
사용자 연락처 가져오는 패키지
그냥 가져올 순 없고 이것도 외부 패키지 도움을 받아야합니다.
저번 시간에 설치한건 그냥 허락만 쉽게 요청하는 간단한 패키지였습니다.
contacts_service 라는 패키지를 설치합시다.
dependencies:
flutter:
sdk: flutter
permission_handler: ^8.2.6
contacts_service: ^0.6.3
pubspec.yaml 파일에 contacts_service를 추가합니다.
그리고 전구버튼에서 pub get 눌러서 패키지 설치하면 됩니다.
import 'package:contacts_service/contacts_service.dart';
그리고 이걸 main.dart에 추가하면 설치 끝입니다.
연락처 다루는 패키지 사용법
이건 외부 패키지 사용법이라
이해가 필요없고 그냥 복붙해서 쓰면 되는 코드들입니다.
var contacts = await ContactsService.getContacts();
이런 코드 짜면 list자료로 모든 연락처를 가져옵니다.
진짠지 확인하고 싶으면 contacts 변수 출력해보셈
아무것도 안들어있으면 폰에서 연락처앱 켜서 연락처 몇개 집어넣어보시길 바랍니다.
출력해보면 [연락처1, 연락처2 ...] 이렇게 나올걸요
var contacts = await ContactsService.getContacts(withThumbnails: false);
근데 연락처에 저장된 썸네일이미지도 다 가져와버리면 너무 느리기 때문에
위 코드를 써서 썸네일 제외하고 가져올 수 있습니다.
await은 왜 붙였게요?
저것도 오래걸리면 못참고 다음 줄 실행하려고 하는 코드라 그렇습니다.
만든 사람이 붙일 수 있대서 붙이는거지 여러분이 임의로 붙이는 문법 아닙니다.
var contacts = await ContactsService.getContacts(withThumbnails: false);
print(contacts[0].givenName)
첫 연락처의 이름 부분을 출력해보는 코드입니다.
어떻게 알았냐면 contacts[0]. 여기까지 치고 ctrl + space 눌러서 자동완성 열어보면 됩니다.
var newContact = new Contact();
newContact.givenName = '민수';
await ContactsService.addContact(newContact);
이 코드 실행하면 실제로 연락처가 폰에 추가됩니다. (new 키워드는 생략가능합니다)
.givenName 항목엔 님 이름
.familyName 항목엔 님 성
이걸 각각 추가해야하는데 이름만 해도 상관없습니다.
실행해보고 연락처앱 들어가보면 진짜로 민수 있음 ㄷㄷ
실제 연락처를 list로 보여주기
Q. 가져온 연락처들을 ListView.builder() 여기에 보여주고 싶으면 어떻게 코드를 짜야할까요?
당연히 name이라는 state안의 자료 갯수만큼 ListView를 생성하고 있기 때문에
님들이 해야할 것은 name이라는 변수에다가 contacts를 넣는 것이겠군요.
(getPermission 함수 안쪽)
var contacts = await ContactsService.getContacts(withThumbnails: false);
print(contacts[0].givenName)
setState(() {
name = contacts;
});
그래서 name이라는 state에 contacts를 넣어봤습니다.
근데 에러가 나는군요
에러 : A value of type 'List<Contact>' can't be assigned to a variable of type 'List<String>'
이건 뭔뜻이냐면
원래 Dart 언어는 타입에 엄격한 언어입니다.
님들이 변수 만들 때
- 문자만 집어넣었다면 앞으로도 문자만 넣을 수 있습니다.
- 숫자만 집어넣었다면 앞으로도 숫자만 넣을 수 있습니다.
- 문자가 담긴 List를 넣었다면 앞으로도 문자가 담긴 List만 넣을 수 있습니다.
변수에 마우스 올려보면 타입을 검사해볼 수 있으며
정수는 int
문자는 String
리스트는 List
문자만 가득 담긴 리스트는 List<String>
이렇게 표현합니다.
그래서 name 변수는 원래 List<String>만 넣을 수 있는데
갑자기 List<Contact> 이렇게 생긴 이상한거 넣지 말라는 에러였습니다.
해결방법은 매우 많은데
1. 타입캐스팅을 사용합니다. 빠르지만 타입을 사기치는거라 좋은 관습은 아닙니다.
2. Union type이라는걸 만듭니다. 근데 Union type은 패키지 설치가 필요해서 패스합시다.
3. 그냥 name 변수를 만들 때 비워놓기
3번으로 합시다.
var name = ['영희', '횟집', '미용실']; 이거를
var name = []; 이걸로 고쳤습니다
이러면 name 변수는 타입이 List<dynamic> 이 됩니다.
dynamic은 그냥 모든 타입을 뜻합니다.
그래서 아무거나 담긴 리스트를 넣을 수 있는 변수가 완성된 것입니다.
(빈 리스트만 집어넣어주면 자동으로 List<dynamic> 타입이 됩니다)
아니면 미리 Contact 타입만 들어올 수 있다~라고 정해놓을 수도 있습니다.
List<Contact> name = [];
이러면 List안에 Contact라는 자료가 들어오는 name 변수를 만들겠습니다~ 라는 뜻입니다.
타입관련 언어들 해보신 분들은 사용하도록 합시다.
타입을 기재하는 이유는 1. 코드 길어지면 버그 미리찾기 쉬움 2. 타입 엄격하게 지정해놓는 경우가 앱이 더 빠름
이런게 있습니다.
타입 좋다고 신나서 막 쓰면 이런 에러가 가끔 보일 수 있습니다.
The argument type 'String?' can't be assigned to the parameter type 'String'
타입에 물음표가 적혀있다면 "String인지 null인지 아직 불확실해서 못사용하겠다~~" 라는 걱정을 해주는 에러입니다.
멋진 개발자 용어로 "null safety 체크" 이라고 합니다.
- 완료버튼 눌러도 Dialog 닫히게
- 빈칸으로 완료버튼 누르면 추가안되게
- 이름옆에 삭제버튼과 기능
- 이름들 가나다순 정렬버튼 (sort함수 사용)
- 전화번호 데이터도 3개 마련해놓고 전화번호도 보여주고 + 입력 수정 삭제 가능
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
// 보통 앱만들때 이렇게 마테리얼 앱을 밖으로 빼줌
home: MyApp() // 이렇게 해도 레이아웃 똑같음 마테리얼 안에 MyApp 즉 Scaffold가 들어가는 것임
));
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var a = 1;
var name = "연락처앱";
var name2 = ["김영숙", "홍길동", "피자집"];
var phone = ["01012341234", "01011111111", "01022222222"];
var like = [0, 0, 0];
var total =3;
AddPhone(New_phone) // 다양한 자료형 입력가능
{
setState(() {
phone.add(New_phone); // 자동 스테이트 추가됨 그러면 자동 UI 반영됨 왜냐 스테이스 수정되면 자동 재 랜더링 하기때문
});
}
AddName(New_name) // 다양한 자료형 입력가능
{
setState(() {
name2.add(New_name); // 자동 스테이트 추가됨 그러면 자동 UI 반영됨 왜냐 스테이스 수정되면 자동 재 랜더링 하기때문
});
}
Remove_name(r_name) // 다양한 자료형 입력가능
{
setState(() {
name2.remove(r_name); // 자동 스테이트 추가됨 그러면 자동 UI 반영됨 왜냐 스테이스 수정되면 자동 재 랜더링 하기때문
});
}
Sort_name() // 다양한 자료형 입력가능
{
setState(() {
name2.sort(); // 자동 스테이트 추가됨 그러면 자동 UI 반영됨 왜냐 스테이스 수정되면 자동 재 랜더링 하기때문
});
}
AddNum() // 다양한 자료형 입력가능
{
setState(() {
total++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Text(total.toString()),
onPressed: () {
showDialog(
barrierDismissible: false, //barrierDismissible - Dialog를 제외한 다른 화면 터치 x
context: context,
builder: (context) {
return DialogUI(AddName : AddName, AddNum : AddNum, AddPhone : AddPhone); // 함수도 이렇게 전송함 변수명 or 함수명만 전송
});
},
),
appBar: AppBar(
title: Text(total.toString()),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
],
),
bottomNavigationBar: Botoom_Main(),
body: ListView.builder(
itemCount: name2.length, // 이거때문에 오류 났음 구현은 했는데 값출력인 안된 이유 3만 반복하라고 해서
// 이걸 동적으로 바꾸면 됨 name2.length 길이만큼
itemBuilder: (context, i) {
return ListTile(
leading: Image.asset('koko1.png'),
title: Container(
width: 300,
child: Row(
children: [
SizedBox(
width: 80,
child: Text(name2[i]),
),
Text(phone[i]),
Container(
width: 200,
padding: EdgeInsets.fromLTRB(10, 3, 20, 3),
child:
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(child: Text("삭제"), onPressed: () {
Remove_name(name2[i].toString());
},),
ElevatedButton(child: Text("정렬"), onPressed: () {
Sort_name();
},),
],
),
),
],
),
),
);
},
),
);
}
}
class DialogUI extends StatelessWidget {
DialogUI({Key? key, this.AddName, this.AddNum, this.AddPhone}) : super(key: key);
final AddName;
final AddNum;
final AddPhone;
var inputData = TextEditingController();
var inputData2 = TextEditingController();
@override
Widget build(BuildContext context) {
return Dialog(
child: SizedBox(
height: 300, width: 300,
//padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment:CrossAxisAlignment.start,
children: [
TextField(
controller: inputData,
),
TextField(
controller: inputData2,
),
TextButton(child: Text("완료"),onPressed: () {
if(inputData.text == "" || inputData2.text == "")
{
Navigator.pop(context);
}
else {
AddName(inputData.text); //.text 를 해서 넘겨 줘야함
AddNum();
AddPhone(inputData2.text); //.text 를 해서 넘겨 줘야함
Navigator.pop(context);
}
}, ),
TextButton(child: Text('취소'),
onPressed: (){ Navigator.pop(context);})
],
),
)
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 여기 context는 무슨 정보 Scaffold + 그리고 Scaffold의 부모인 마테리얼 앱 같은게 있을것임
return Container();
}
}
class Botoom_Main extends StatelessWidget {
const Botoom_Main({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white, //색상
child: Container(
height: 70,
padding: EdgeInsets.only(bottom: 10, top: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.call),
Icon(Icons.message),
Icon(Icons.contacts),
],
),
),
);
}
}