
//
import 'dart:convert';
import 'package:flutter_practice_1/weather.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart';
// 1. 홈페이지에 보여지는 상태 변수들을 담는 클래스
class HomeState {
Weather? weather;
HomeState({required this.weather});
}
// 2. 상태를 관리하는 클래스 => 1번 클래스 객체를 실제로 담고 있는 클래스
// 이거는 riverpod 패키지의 사용 방법
// Notifier < 관리해야하는 상태의 클래스명 >
class HomeViewModel extends Notifier<HomeState> {
HomeState build() {
return HomeState(weather: null);
}
void loadWeather() async {
// 1. 데이터 달라고 편지쓰기 > http 통신 GET 요청
Client httpClient = Client(); // http 통신을 하기위한 객체
Uri taget = Uri.parse(
"https://api.open-meteo.com/v1/forecast?latitude=37.57&longitude=126.98¤t_weather=true",
);
Response r = await httpClient.get(taget);
// 2. 정보가 담긴 답장 받기
print(r.statusCode);
//편지가 정상적으로 전달되고 답장을 받았는지 알려주는 숫자코드
print(r.body);
// 성공 : 200 , 실패 : 404,
// 3. 답장내용(JSON) > Map 으로 변경
Map<String, dynamic> jsonMap = jsonDecode(r.body);
// 4. Map 객체로 변경 (3 ~ 4 : 역직렬화)
Weather w = Weather.fromJson(jsonMap);
state = HomeState(weather: w);
//
}
//
}
// 로직과 상태가 분리가 완료됨
// 이제부턴 사용에 관한 내용임
// 의존성 주입
// 객체를 HomePage 에서 직접 생성하면 여전히 자녀위젯에 생성자를 통해 전달 해줘야흐는 불편함이 있음
// 객체를 직접 생성하지 않고 공급해주는 NotivierProvider 사용 => 의존성 주입
//
// NotivierProvider는 공급해줄 객체 (ViewModerl)의 타입과 상태클래스 타입을 제너릭으로 명시해줘야함
final HomeViewModelProvider = NotifierProvider<HomeViewModel, HomeState>(
//
() {
print("homeviewmodel 생성됨");
return HomeViewModel();
//
},
);
Dart 코드로 되어있는 객체를 다른 언어로 작성한 언어 및 플랫폼(예를들어 자바로 작성된 서버)에서 작동하게 하려면?
JSON 이라는 형식 주로 사용JS Object : 중괄호 안에 key - value 쌍
{
"name": "오상구",
"age": 7
}
key 는 반드시 문자열value 에 들어갈 수 있는 타입StringNumber(int, double)Boolean(bool)Array(대괄호)Object(중괄호)null{
"name": "오상구",
"age": 7,
"isMale" : true,
"favorite_foods" : ["삼겹살", "연어", "고구마"],
"dislike_foods" : [],
"contact": {
"mobile": "010-0000-0000",
"email": null
}
}JSON형식의 String으로 주고 받음Dart 객체 → Map → StringString → Map → Dart 객체객체를 바로 String으로 변환 하려면 개발가가 직접 구현해야함 ⇒ 복잡다트SDK 의 dart:convert 라이브러리에 jsonEncode , jsonDecode 함수가 있는데 Map, List 를 String으로 변환해주거나 Map, List 를 String 등으로 변환해줌{
"name": "이지원",
"age": 20
}
[
{
"name": "이지원",
"age": 20
},
{
"name": "오상구",
"age": 7
}
]
import 'dart:convert';
void main() {
// 여러줄의 문자열, 따옴표 등을 편하게 사용할 수 있는 트리블 쿼트 """, '''
String jsonString = """
{
"name": "이지원",
"age": 20
}
""";
// 변환되는 타입을 확인하기 타입 추론 var 사용
// jsonDecode 리턴 타입이 dynamic 이니까 확인
var result = jsonDecode(jsonString);
print(result); // {name: 이지원, age: 20}
print(result.runtimeType); // Map<String, dynamic>
}
import 'dart:convert';
void main() {
// 여러줄의 문자열, 따옴표 등을 편하게 사용할 수 있는 트리블 쿼트 """, '''
String jsonString = """
[
{
"name": "이지원",
"age": 20
},
{
"name": "오상구",
"age": 7
}
]
""";
// 변환되는 타입을 확인하기 타입 추론 var 사용
// jsonDecode 리턴 타입이 dynamic 이니까 확인
var result = jsonDecode(jsonString);
print(result); // [{name: 이지원, age: 20}, {name: 오상구, age: 7}]
print(result.runtimeType); // List<dynamic>
}
Map 에서 계속 키값으로 찾아야돼서 타입이 헷갈리고 오타 발생할 수 있음!class User {
User({
required this.name,
required this.age,
});
String name;
int age;
}
class User {
User({
required this.name,
required this.age,
});
String name;
int age;
User.fromJson(Map<String, dynamic> map)
: this(
name: map['name'],
age: map['age'],
);
Map<String, dynamic> toJson() {
return {
"name": name,
"age": age,
};
}
}
import 'dart:convert';
void main() {
String jsonString = """
{
"name": "지은성",
"age": 20
}
""";
var jsonMap = jsonDecode(jsonString);
User user = User.fromJson(jsonMap);
print(user.toJson()); // {name: 지은성, age: 20}
}
class User {
User({
required this.name,
required this.age,
});
String name;
int age;
User.fromJson(Map<String, dynamic> map)
: this(
name: map['name'],
age: map['age'],
);
Map<String, dynamic> toJson() {
return {
"name": name,
"age": age,
};
}
}
void main() {
//
String easyJson = """
{
"name": "오상구",
"age": 7,
"isMale" : true
}
""";
}
import 'dart:convert';
void main() {
//
String easyJson = """
{
"name": "오상구",
"age": 7,
"isMale" : true
}
""";
// 1. Map<String, dynamic>으로 변환해서 map이라는 이름의 변수에 담기
// 3. jsonString 을 Map 으로 변환 후 Pet class로 변환 후 출력해서 변수에 담기!
// 4. print 문으로 Pet 클래스의 toJson 메서드를 호출해서 출력
}
/*
* 2. 아래에서 클래스 정의 Pet
* (함수 내에서는 클래스를 정의할 수 없습니다!)
*/
void main() {
//
String hardJson = """
{
"name": "오상구",
"age": 7,
"isMale" : true,
"favorite_foods" : ["삼겹살", "연어", "고구마"],
"contact": {
"mobile": "010-0000-0000",
"email": null
}
}
""";
}
import 'dart:convert';
void main() {
String hardJson = """
{
"name": "오상구",
"age": 7,
"isMale" : true,
"favorite_foods" : ["삼겹살", "연어", "고구마"],
"contact": {
"mobile": "010-0000-0000",
"email": null
}
}
""";
// 1. Map<String, dynamic>으로 변환해서 map이라는 이름의 변수에 담기
// 3. jsonString 을 Map 으로 변환 후 Pet class로 변환 후 출력해서 변수에 담기!
// 4. print 문으로 Pet 클래스의 toJson 메서드를 호출해서 출력
}
/*
* 2. 아래에서 클래스 정의 Contact, Pet
* (함수 내에서는 클래스를 정의할 수 없습니다!)
*/
import 'dart:convert';
void main() {
String jsonString = """
{
"name": "오상구",
"age": 7,
"isMale" : true,
"favorite_foods" : ["삼겹살", "연어", "고구마"],
"contact": {
"mobile": "010-0000-0000",
"email": null
}
}
""";
// 2. Map<String, dynamic>으로 변환해서 map이라는 이름의 변수에 담기
Map<String, dynamic> map = jsonDecode(jsonString);
// 3. jsonString 을 Map 으로 변환 후 Pet class로 변환 후 출력해서 변수에 담기!
Pet pet = Pet.fromJson(map);
// 4. print 문으로 Pet 클래스의 toJson 메서드를 호출해서 출력
print(pet.toJson());
}
/*
* 1. 아래에서 클래스 정의 Contact, Pet
* (함수 내에서는 클래스를 정의할 수 없습니다!)
*/
class Pet {
final String name;
final int age;
final bool isMale;
final List<String> favoriteFoods;
final Contact contact;
Pet({
required this.name,
required this.age,
required this.isMale,
required this.favoriteFoods,
required this.contact,
});
Pet.fromJson(Map<String, dynamic> json)
: this(
name: json["name"],
age: json["age"],
isMale: json["isMale"],
// jsonDecode는 List타입을 반환할 때 List<dynamic> 타입으로 반환하기 때문에
// List<String> 타입으로 리스트 변환
favoriteFoods: List<String>.from(json["favorite_foods"]),
contact: Contact.fromJson(json["contact"]),
);
Map<String, dynamic> toJson() {
return {
"name": name,
"age": age,
"isMale": isMale,
"favorite_foods": favoriteFoods,
"contact": contact.toJson(),
};
}
}
class Contact {
String mobile;
String? email;
Contact({
required this.mobile,
required this.email,
});
Contact.fromJson(Map<String, dynamic> json)
: this(
mobile: json["mobile"],
email: json["email"],
);
Map<String, dynamic> toJson() {
return {
"mobile": mobile,
"email": email,
};
}
}
Repository 만들어서 가지고와서 JSON을 객체로 변환해서 반환Repository)에서 데이터(Model 클래스)를 가지고 와서 가공하는 계층

class HomeState{
int counter;
HomeState(this.counter);
}
// Notifier를 상속받으면 상태를 저장하고 업데이트할 수 있는 기능을 갖는다(ViewModel의 구실 가능)
// (Notifier 상속 시 이 ViewModel 이 어떤 상태를 관리하는지 제너릭으로 지정)
class HomeViewModel extends Notifier<HomeState> {
// Notifier 클래스의 build 메서드를 재정의 해서
// 초기 상태값 return 해주기
// => HomeViewModel 이 생성되면 build 메서드의 리턴값으로 상태가 초기화
HomeState build() {
return HomeState(1);
}
void updateState(){
// 상태를 업데이트 해줄 땐 새로운 객체를 만들어 변경
state = HomeState(state.counter + 1);
// 새로운 객체를 state에 할당하지 않으면 자신을 바라보는 View에게
// 상태가 변경되었다고 알리지 않음
// state.coutner++; // => X
}
}
// 뷰에서는 이 공급자에게 ViewModel 달라고 요청. => 뷰모델 관리자
final homeViewModelProvider
// 이 뷰모델 관리자가 관리해야할 뷰모델의 타입과 뷰모델이 관리하는 상태 제너릭으로 명시
= NotifierProvider<HomeViewModel, HomeState>(
// 생성자에 뷰모델을 생성하는 함수를 넘겨줌
// 뷰에서 관리자에게 뷰모델 달라고 요청할 때 실행되어 뷰모델이 생성됨
// 단 기존에 생성이 되어있으면 기존에 생성된걸 전달
(){
return HomeViewModel();
});
Consumer(
builder: (context, ref, child){
// 상태 사용 할 때
// 1. ref.read(homeViewModelProvider) : 상태 1회성으로 읽고 변경은 감지 X
// 2. ref.watch(homeViewModelProvider) :
// 상태변경 감지해서 상태 변경 일어날때마다 재빌드
// 뷰모델에 접근할 때
// ref.read(homeViewModelProvider.notifier)
ref.watch(homeViewModelProvider);
return Column(children:[
Text()
]);
}
)
뷰모델 관리자(공급자)가 뷰모델을 제공할 수 있게 최상위 위젯 ProviderScope로 감싸기
void main() {
// 이 앱에서 ViewModel을 RiverPod이 관리하게 해주게 해줌
runApp(const ProviderScope(child: MyApp()));
}
flutter pub add flutter_riverpodProviderScope 로 감싸기Widget 구현Model 클래스 만들기Repository 클래스 만들기Widget 에서 관리될 상태 클래스 만들기ViewModel 만들기(Notifier 상속)build 메서드 재정의 해서 설정Repotory에서 데이터 가져와서 상태 업데이트 하는 함수 작성viewModelProvider(NotifierProvider 객체) 만들기Widget 에서 Consumer 위젯 사용해서 데이터 씌우기 및 함수 연결ref.watchref.readref.read 에 viewModelProvider.notifier 넘겨주기import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomeState {
HomeState({
required this.counter,
required this.updateTime,
});
int counter;
DateTime? updateTime;
}
// Notifier<이 뷰모델에서 관리할 상태 타입>
class HomeViewModel extends Notifier<HomeState> {
HomeState build() {
return HomeState(
counter: 0,
updateTime: null,
);
}
}
// ViewModel 공급자
// Notifier<뷰모델 타입, 뷰모델에서 관리할 상태 타입>
final homeViewModelProvider = NotifierProvider<HomeViewModel, HomeState>(() {
return HomeViewModel();
});
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomeState {
HomeState({
required this.counter,
required this.updateTime,
});
int counter;
DateTime? updateTime;
}
// AutoDisposeNotifier<이 뷰모델에서 관리할 상태 타입>
class HomeViewModel extends AutoDisposeNotifier<HomeState> {
HomeState build() {
return HomeState(
counter: 0,
updateTime: null,
);
}
}
// ViewModel 공급자
// NotifierProvider.autoDispose<뷰모델 타입, 뷰모델에서 관리할 상태 타입>
final homeViewModelProvider =
NotifierProvider.autoDispose<HomeViewModel, HomeState>(() {
return HomeViewModel();
});
AutoDisposeNotifier 와 동일하나 ViewModel 최소 생성 시 Widget에서 ViewModel에 값을 넘겨서 무언가를 처리하고 싶을 때import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_state_exmaple/user.dart';
class HomeState {
HomeState({
required this.user,
required this.updateTime,
});
User? user;
DateTime? updateTime;
}
// AutoDisposeNotifier<이 뷰모델에서 관리할 상태 타입, 뷰로부터 넘겨받을 값의 타입>
class HomeViewModel extends AutoDisposeFamilyNotifier<HomeState, int> {
HomeState build(int arg) {
return HomeState(
user: null,
updateTime: null,
);
}
void fetchUserInfo(int userId) {
// 값을 넘겨받아 데이터를 요청해야할 경우
// repository.userId(userId)
}
}
// ViewModel 공급자
// NotifierProvider.autoDispose.family<뷰모델 타입, 뷰모델에서 관리할 상태 타입, 뷰모델에서 넘겨받을 데이터 타입>
final homeViewModelProvider =
NotifierProvider.autoDispose.family<HomeViewModel, HomeState, int>(() {
return HomeViewModel();
});
////========================= Widget
// Widget 에서 사용할 땐 homeViewModelProvider에 값을 넘겨줌
Consumer(
builder: (context, ref, child) {
HomeState homeState = ref.watch(homeViewModelProvider(1));
return Text('');
},
);
TextEditingController textEditingController = TextEditingController();
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
// 빈화면 클릭시 키보드 포커스 사라지게 해줌
FocusScope.of(context).unfocus();
},
child: Scaffold(
appBar: AppBar(
title: TextField(
maxLines: 1,
controller: textEditingController,
decoration: InputDecoration(
hintText: "검색어를 입력해 주세요",
border: WidgetStateInputBorder.resolveWith(
(states) {
// 포커스 받지 않았을때 연하게, 포커스 받았을때 진하게
if (states.contains(WidgetState.focused)) {
return OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: Colors.black),
);
}
// 포커스 받지 않았을때 연하게, 포커스 받았을때 진하게
return OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: Colors.grey),
);
},
//
),
),
),
)
class _HomePageState extends State<HomePage> {
TextEditingController textEditingController = TextEditingController();
void dispose() {
// textEditingController 사용시에는 반드시 dispose 호출해줘야함
textEditingController.dispose();
super.dispose();
}
onTap() {
FocusScope.of(context).unfocus();
} < 내에 입력
GridView.builder(
padding: EdgeInsets.all(20),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 3 / 4,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemBuilder: (context, index) {
return Image.network('https://picsum.photos/200/300');
},
//
),
showModalBottomSheet(
// 내부적으로 네비게이터 스택에 페이지를 쌓는 형식이라서 BuildContext 넘겨줌.
// 페이지 이동 시 Navigator.push(context... 썼던거 기억 되살립시다!
context: context,
// 배경 터치했을 때 바텀시트 닫을지 여부
isDismissible: true,
builder: (context) {
// 바텀시트 내 배치할 위젯
// Column 은 기본적으로 children 중 가장 큰 위젯을 기준으로 사이즈를 정하므로
// SizedBox로 바텀시트의 사이즈를 정해줍니다
return SizedBox(
height: 200,
width: double.infinity,
child: Column(
children: [
Text('test'),
],
),
);
},
)

flutter_webview vs flutter_inappwebviewflutter_webview : flutter 공식 팀에서 만들고 관리하지만 기능이 많이 없음flutter_inappwebview : flutter 공식팀이 만든건 아니지만 쿠키 관리, 자바스크립트 상호작용 등의 기능을 간편하게 할 수 있음InAppWebView(
// InAppWebView 최초 요청할 URL
initialUrlRequest: URLRequest(
url: WebUri("https://www.naver.com/"),
),
// WebView 설정 : 외우지 마시오! 버전바뀌면 사용법 바뀌니까 이런설정을 할 수 있다 정도로만 학습바랍니다!
initialSettings: InAppWebViewSettings(
// 사용자 제스쳐 없이도 비디오, 오디오 자동재생 가능여부
mediaPlaybackRequiresUserGesture: true,
// 페이지 javascript 활성화여부. 웹 브라우저에서는 js 필수불가결한 요소라 true!
javaScriptEnabled: true,
// 요청하는 클라이언트의 브라우저 종류, 운영체제, 장치 정보 등을 서버에서 알 수 있게 보낼때 같이 보냄
// 디폴트로 웹뷰로 되어 있는데 일부 웹페이지에서는 웹뷰로 접속시 차단하는 페이지도 있으니 꼭 설정!
userAgent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
),
// WebView 네이티브 컴포넌트가 만들어지면 호출됨
onWebViewCreated: (controller) {
print('onWebViewCreated');
},
// 페이지 로딩이 시작될 때 호출됨
onLoadStart: (controller, url) {
print('onLoadStart');
},
// 페이지 로딩이 완료되면 호출됨
onLoadStop: (controller, url) {
print('onLoadStop');
},
// 웹뷰 내 웹 페이지에서 GPS, 카메라 등의 권한을 요청했을때 호출됨
onPermissionRequest: (controller, request) async {
print('onPermissionRequest');
return null;
},
)