혼자 개발하다보면 내가 잘 이해하고 있는지 아닌지 확인할 방법이 사실 잘 없다. 지인들에게 물어보기에는 flutter & dart 가 아직까지는 많이 사용하지 않기 때문도 있다. 그래서 velog 에 글을 남겨 많은 사람들로 부터 조언과 피드백을 받고 싶다.(물론 많이 안 읽겠지만... 😂)
일단 초보자를 기준으로 설명하기 때문에 간단한 예제이더라도 상세하게 내가 실제로 겪은 오류에 대해서도 설명할 예정이다.
Mac, Android Studio, Ios Simulator,
Android emulator
개발은 보시는 것과 같이 맥에서 안드로이드 스튜디오를 사용해서 개발하였다. 실행은 Android, Ios 환경에서 모두 해보았다. Android는 내폰으로 디버깅했다. 에뮬레이터로 돌려보니 에뮬레이터 네트워크 설정을 해줘야하는데 나는 해봤는데 안되서 그냥 내 샘송 폰으로 돌려봄....(맥북 왜 쓰냐고~~)
pub.dev에서
3개의 라이브러리를 사용했다.
일단은 초보자더라도 기본은 알고있더라고 가정하고 설명하겠습니다
(자꾸 말투가 왔다갔다하는거 이해해주세용 ㅠㅠㅠㅠ🙈)
PlatformWidget 들을 사용해서 Android, Ios 모두 사용가능한 예제이다.
대충 그럴듯한 이름의 프로젝트를 하나 생성해보자.
그리고 main.dart에 있는 모든 파일을 지우고 pubspec.yaml 에서 라이브러리들을 사용 가능하도록 설정하자
그리고 main.dart 에 다음의 코드를 작성하자
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// 각 플랫폼별 테마 설정
final cupertinoTheme = new CupertinoThemeData(
primaryColor: CupertinoDynamicColor.withBrightness(
color: Colors.purple,
),
);
final materialTheme = new ThemeData(
primarySwatch: Colors.purple,
);
Widget build(BuildContext context) {
return PlatformApp(
material: (context, platform) => MaterialAppData(
theme: materialTheme,
),
cupertino: (context, platform) => CupertinoAppData(
theme: cupertinoTheme,
),
home: MainPage(),
);
}
}
만약 AppClass 이름을 'MyApp'이 아닌 다른 이름으로 생성했으면 test/widget_test.dart에 오류가 하나 생길텐데 그냥 오류가 난 부분의 클래스를 MyApp이 아닌 다른이름으로 만든 클래스로 바꿔주면 된다.(모르겠으면 걍 MyApp으로 하자)
PlatformWidget 들은 이 위젯들이 알아서 Android 면 Material 로, Ios 면 Cupertino 로 설정해준다. 그래서 PlatformApp은 실행 환경이 Ios면 CupertinoApp 으로, Android면 MaterialApp 으로 컴파일러에서 자동적으로 바꿔준다고 생각하면 된다.👀
이제 main_page.dart 파일을 하나 생성하고 MainPage 클래스를 StatefulWidget 으로 만들자.
그리고 Widget builld() 함수 안에다가 다음 코드를 넣자
build(BuildContext context) {
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(
'text'
),
),
body: Center(
child: PlatformText(
'text';
),
),
);
}
Widget
기본적인 Scaffold 화면이 생성될 것이다. 그러고 나서 이제 서버로 부터 데이터 리스트들을 받아올 준비를 하자.
main_page.dart 파일 위에다가 http 라이브러리를 import 할건데 다음과 같이 한다.
import 'package:http/http.dart' as http;
이러면 http 라이브러리의 함수들을 http.~~~ 로 사용 가능하다.
뭔가 제목으로 쓸게 없어서 그냥 썼다.
JSONPlaceholder 라는 사이트에서 rest api를 테스트 용으로 사용 할 수 있다.
get, post, delete, patch, put 모두 테스트 가능하니 나중에 따로 해보시길 바란다.
아무튼 우리는 오늘 https://jsonplaceholder.typicode.com/posts 이 주소로
userId, id, title, body 4개의 데이터를 리스트 형식으로 받아올 것이다.
일단 데이터를 받아오기 위해 간단한 data class를 하나 만들도록 하자.
class Post {
final int userId;
final int id;
final String title;
final String body;
Post({this.userId, this.id, this.title, this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
userId: json["userId"],
body: json["body"],
title: json['title']
);
}
}
생성자에 중괄호로 한번 감싸주는 이유는 생성자에 인자(argument)를 넣을 때 순서대로 넣지 않고 아래 fromJson() 함수와 같이 인자를 대입할 수 있기 때문이다.
그렇다면 fromJson()은 어떤 역할은 하는 걸까?
우선 factory 키워드는 클래스의 인스턴스를 생성 할 때 항상 새로운 인스턴스를 생성하지 않는 생성자를 구현할 때는 factory 키워드를 사용한다.
이러한 내용들을 알아두면 좋겠지만 그냥 쉽게 생각해서 자원의 효울을 위해 factory를 사용한다고 생각하자.
Post.fromJson() 는 map 구조에서 Post 객체를 생성해내는 생성자이다.
쉽게 설명하자면 flutter 에서 json값을 처리할 때 dart 의 기본 라이브러리 중 하나인 'convert' 를 통해서 처리한다. 나중에 코드를 보면 이해 하겠지만 이때 map 형식의 데이터를 추출해 다시 fromJson 생성자를 이용하여 Post 객체 데이터를 뽑아내는 형식이다.
그냥 코드나 보자...🙈
_TestWidgetState 클래스에 아래의 코드를 작성해보자
String _bodyText = '';
void initState() {
super.initState();
_fetchPosts();
}
Widget biuld(...)
void _fetchPosts() async {
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
_bodyText = response.body;
}
이러면 json data 들이 string 형식으로 쭉 나올 것이다.
만약 오류는 없는데 String 값이 보이지 않는다면 Center 위젯 안에 코드를 다음으로 바꾸어 보자.
child: SingleChildScrollView(
child: PlatformText(
_bodyText,
style: TextStyle(
color: Colors.black
),
),
),
그러면 위 이미지와 같은 화면이 생성될 것이다.
그런데 저렇게 만드니깐 살짝 지저분하게 보인다. 어떻게 하면 좀 더 깔삼하게 만들 수 있을까? 그냥 Listview.builder 와 Provider 패턴을 사용하면 코드와 화면 Ui를 깔끔하게 만들 수 있다.
Provider 는 flutter 에서 사용하는 디자인 패턴 중 하나이다. 디자인 패턴이라고 해서 어려운 것이 아니다. 그냥 따라 해볼 사람은 따라 해보기 바란다.
post_provider.dart 파일을 생성 한 다음에 아래 코드를 입력하자.
class PostProvider with ChangeNotifier {
List<Post> _posts = [];
List<Post> getPostList() {
_fetchPosts();
return _posts;
}
// data 세팅
void _fetchPosts() async {
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
final List<Post> parsedResposne = jsonDecode(response.body).
map<Post>((json) => Post.fromJson(json)).toList();
_posts.clear();
_posts.addAll(parsedResposne);
notifyListeners(); // data 가 수정되었다고 알려주기
}
}
문법에 대한 부분은 나중에 상세하게 따로 정리해서 올리겠다.
쉽게 설명하자면(자꾸 쉽게 설명한대면서 길고 장황하게 써서 죄송합니다. ㅠㅠ😭) main_page.dart 파일의 http 코드를 provider 클래스로 옮겨서 나중에 main_page 에서 다시 호출하면 받아온 데이터를 넘겨주는 구조이다.
build(BuildContext context) {
final postProvider = Provider.of<PostProvider>(context);
var postDatas = postProvider.getPostList();
// _fetchPosts();
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(
'text'
),
),
body: Center(
child: ListView.builder(
itemCount: postDatas.length,
itemBuilder: (context, index) {
final post = postDatas[index];
return listTitle(post.title, post.body);
},
),
),
);
}
Widget listTitle(String title, String subtitle) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PlatformText(
title,
style: TextStyle(
fontSize: 15.0,
),
),
PlatformText(
subtitle,
overflow: TextOverflow.visible,
style: TextStyle(
fontSize: 12.0,
),
)
],
),
);
}
Widget
여기서 혹자는 ListTile 위젯은 이미 있는데 왜 만들어 쓰냐고 할 수있는데 ios 에서는 ListTile 위젯을 쓰면 오류가 나기 때문이다.(;;;;;)
TMI => dart 에서 세미콜론 여러개 찍어도 오류 안뜸
(뜨면 ㅈㅅ)
그리고 마지막으로 main.dart 코드로 가서
MainPage() 에다가 Alt + Enter(맥은 Option + Enter) 로 Wrap the Widget 즉, 새로운 위젯으로 감싸주는데 그냥 다음과 같이 고쳐주면 된다.
home: ChangeNotifierProvider<PostProvider>(
create: (context) => PostProvider(),
child: MainPage(),
),
무슨 뜻이냐면 걍 MainPage() 위젯에서 PostProvider 를 쓴다는 의미이다. 이렇게 하면 다음과 같은 완성물이 나온다.
이번 포스트에 마저 담지 못한 내용들을 다시 정리해서 속편으로 하나 작성해야겠다.
오류가 있으면 답글 달아주시길 바랍니다. 감사합니다.👍