Flutter 캠프 28일차

은성·2026년 1월 5일

Flutter 본캠프

목록 보기
30/57
post-thumbnail

모닝스터디

riverpod

//
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&current_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();
    //
  },
);

숙련강의 시작

JSON

  • JavaScript Object Notation
  • JavaScript의 객체 표기 방식을 기반

Dart 코드로 되어있는 객체를 다른 언어로 작성한 언어 및 플랫폼(예를들어 자바로 작성된 서버)에서 작동하게 하려면?

  • 직렬화(Serialization)
    • 객체를 서버에 전송하거나 로컬 저장소에 저장하기 위해 적합한 형식으로 변환하는 과정
    • 서로다른 언어를 사용하는 플랫폼에서도 데이터를 주고받을 수 있게 하기 위해 JSON 이라는 형식 주로 사용

사용법

JS Object : 중괄호 안에 key - value 쌍

{
	"name": "오상구",
	"age": 7
}
  • key 는 반드시 문자열
  • value 에 들어갈 수 있는 타입
    • String
    • Number(int, double)
    • Boolean(bool)
    • Array(대괄호)
    • Object(중괄호)
    • null
    • ex) 강아지 정보를 JSON 형태로 표현
      {
      			"name": "오상구",
      			"age": 7,
      			"isMale" : true,
      			"favorite_foods" : ["삼겹살", "연어", "고구마"],
      			"dislike_foods" : [],
      			"contact": {
      				"mobile": "010-0000-0000",
      				"email": null
      			}
      }

Dart 에서 사용법

Dart에서 JSON 데이터 변환 형식

  • 데이터를 주고 받을 때 JSON형식의 String으로 주고 받음

직렬화

  • Dart 객체MapString

역직렬화

  • StringMapDart 객체

Map을 거치는 이유

  • 객체를 바로 String으로 변환 하려면 개발가가 직접 구현해야함 ⇒ 복잡
  • 다트SDKdart:convert 라이브러리에 jsonEncode , jsonDecode 함수가 있는데 Map, ListString으로 변환해주거나 Map, ListString 등으로 변환해줌

jsonEncode 사용법

{
	"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>
}

Dart 객체로 변환

  • User class 만들기
  • Dart 객체로 변환해서 쓰는 이유
    - 객체로 변환하지 않으면 Map 에서 계속 키값으로 찾아야돼서 타입이 헷갈리고 오타 발생할 수 있음!
class User {
	User({
    required this.name,
    required this.age,
  });
 
	String name;
	int age;
}

class 에 fromJson named 생성자 , toJson 메서드 구현해서 사용

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,
    };
  }
}

MVVM

  • Model + View + ViewModel 의 약자
  • Model, View, ViewModel 로 계층을 나누어서 개발
  • Model : 데이터를 서버 등에서 가지고 오는 계층, 데이터 클래스 그 자체도 포함
    • JSON 예제에서 User, Pet 클래스 ⇒ Model 계층. 데이터 클래스를 Model이라고도 부름
    • JOSN 예제에서는 서버에서 가지고 오지 않았지만 서버에서 가지고올 때 각 모델(데이터 클래스)별로 Repository 만들어서 가지고와서 JSON을 객체로 변환해서 반환
  • View : 화면을 구현하는 계층
    • 위젯
  • ViewModel : Model 계층(Repository)에서 데이터(Model 클래스)를 가지고 와서 가공하는 계층

MVVM과 StatefulWidget 차이

StatefulWidget

  1. 사용자 클릭, 로직 처리
  2. 데이터 요청해서 데이터 받음
  3. 받은 데이터 가공 및 상태 업데이트
  4. 화면 업데이트
  • StatefulWidget에서 많은 역할 수행 ⇒ 코드 지저분해짐

MVVM 동작원리

  1. ViewModel 구독(변경되는지 바라보고 있음)
  2. 사용자 클릭했으니 처리해!
  3. 로직 처리
  4. 데이터 요청해서 데이터 받음
  5. 받은 데이터 가공 및 상태 업데이트
  6. 자신의 상태가 바뀌었다고 알림(View가 누군지는 모름)
  7. 뷰는 ViewModel을 구독하고 있기 때문에 상태가 바뀌었다는걸 감지. 화면 업데이트!

효과

  • 각각 계층의 역할에 맞게 코드를 작성하기 때문에 코드 깔끔해짐
    • 유지보수 편해짐
    • 오류발생 감소
    • 중복코드 감소
  • 각각 역할이 분리되어있으니 각자 역할에 맞는 테스트 작성 가능
  • 서로 결합도(View는 ViewModel 을 참조하지만 ViewModel은 어떤 View가 참조하는지 모름)이 낮아져서 테스트 작성 용이

Flutter에서 MVVM 구현법

  • Flutter 에서 자체 제공하는 기능으로 구현 가능하지만 상태관리 라이브러리를 쓰면 편하게 구현가능
  • RiverPod 이란 상태관리 라이브러리 사용

Riverpod

  • 개발자가 상태관리를 편하게 할 수 있도록 도와주는 라이브러리
  • ViewModel 의 역할(상태를 관리하고 변경되었음을 알려주는 역할)을 쉽게 구현가능
  • View(Widget)에서 ViewModel의 관찰을 쉽게 하게 해줌

사용법

ViewModel 만드는 법

  • 관리할 데이터를 담을 상태 클래스를 만든다
class HomeState{
	int counter;
	HomeState(this.counter);
}
  • Notifier를 상속 받는 ViewModel class를 만든다
// 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
  }
}
  • 뷰모델을 관리 및 공급해 줄 NotifierProvider 객체를 변수에 담아준다
// 뷰에서는 이 공급자에게 ViewModel 달라고 요청. => 뷰모델 관리자
final homeViewModelProvider 
	// 이 뷰모델 관리자가 관리해야할 뷰모델의 타입과 뷰모델이 관리하는 상태 제너릭으로 명시
	= NotifierProvider<HomeViewModel, HomeState>(
	// 생성자에 뷰모델을 생성하는 함수를 넘겨줌
	// 뷰에서 관리자에게 뷰모델 달라고 요청할 때 실행되어 뷰모델이 생성됨
	// 단 기존에 생성이 되어있으면 기존에 생성된걸 전달
	(){
		return HomeViewModel();
	});

Widget에서 사용법

  • Consumer 위젯을 사용해 ViewModel에 접근
    뷰모델 관리자에게 뷰모델 혹은 상태를 달라고 요청하기 위해서 WidgetRef가 필요하기 때문
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()));
}

Riverpod 장점 및 복습

장점

  • 하위 위젯에 불필요하게 파라미터 넘겨줄 필요 없어짐(하위 위젯에서 Consumer 쓰면됨)
  • 전역 상태 관리 가능(ViewModel 공급을 riverpod이 해주니깐)
  • 위젯을 그리는 코드는 Widget에서, 상태 관리는 별도의 클래스에서 하니 유지보수 용이

통신 시 사용법 정리

  • RiverPod 패키지 추가 flutter pub add flutter_riverpod
  • main 함수에서 최상위 위젯을 ProviderScope 로 감싸기
  • Widget 구현
  • 데이터를 담을 Model 클래스 만들기
  • 모델 가져오는 Repository 클래스 만들기
  • Widget 에서 관리될 상태 클래스 만들기
  • 상태를 관리할 ViewModel 만들기(Notifier 상속)
    • 최초 상태 build 메서드 재정의 해서 설정
    • Repotory에서 데이터 가져와서 상태 업데이트 하는 함수 작성
  • ViewModel을 공급할 viewModelProvider(NotifierProvider 객체) 만들기
  • Widget 에서 Consumer 위젯 사용해서 데이터 씌우기 및 함수 연결
    • 상태 업데이트 될 때 위젯이 변경되길 원하면 ref.watch
    • 한번만 값을 받아오고 싶으면 ref.read
    • ViewModel의 메서드를 사용하고 싶으면 ref.readviewModelProvider.notifier 넘겨주기

ViewModel 로 사용할 수 있는 Notifier 종류

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();
});

AutoDisposeNotifier

  • 참조하기 시작한 시점부터 처음 참조한 위젯이 화면에서 없어질때 같이 없어짐
  • 로그인 페이지 → 회원가입 페이지 → 로그인 페이지로 뒤로가기 → 회원가입 페이지
    이 경우에 회원가입 입력정보를 초기화 하고 싶을 때

사용법

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();
});

AutoDisposeFamilyNotifier

  • AutoDisposeNotifier 와 동일하나 ViewModel 최소 생성 시 Widget에서 ViewModel에 값을 넘겨서 무언가를 처리하고 싶을 때
  • 블로그 목록 페이지 → 블로그 포스트 상세 페이지 로 이동할 때 어떤 블로그 포스트의 정보를 불러오는지 뷰모델에 넘겨주고 싶을 때
  • 블로그 포스트 상세 페이지 → 블로그 포스트 상세 페이지(다른글) 로 이동할 때 FamilyNotifier를 사용하지 않으면 기존의 ViewModel을 공급받지만 FamilyNotifier를 사용하면 블로그 글이 다르면 새로운 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('');
    },
  );

책검색 앱 만들기

TextFild

  • 포커스 받지 않았을때 연하게, 포커스 받았을때 진하게
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),
                  );
                },
                //
              ),
            ),
          ),
        )

textEditingController

  • textEditingController 사용시에는 반드시 dispose 호출해줘야함
class _HomePageState extends State<HomePage> {
  TextEditingController textEditingController = TextEditingController();

  
  void dispose() {
    // textEditingController 사용시에는 반드시 dispose 호출해줘야함
    textEditingController.dispose();
    super.dispose();
  }

obscureText : true

  • 비밀번호 입력시 텍스트를 가릴 수 있는 효과

FocusScope

  • 빈화면 클릭시 키보드 포커스 사라지게 해줌
onTap() { 
 	FocusScope.of(context).unfocus();
 } < 내에 입력

GridView (숙련 2-3)

  • 격자 형태 뷰
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');
          },
          //
        ),

ShowBottomSheet (숙련 2-3)

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'),
        ],
      ),
    );
  },
)

home

  • 이렇게 주석처리하고 하면 편함

WebView (숙련 2-4)

  • flutter_webview vs flutter_inappwebview
    • flutter_webview : flutter 공식 팀에서 만들고 관리하지만 기능이 많이 없음
    • flutter_inappwebview : flutter 공식팀이 만든건 아니지만 쿠키 관리, 자바스크립트 상호작용 등의 기능을 간편하게 할 수 있음

패키지 추가

  • flutter pub add flutter_inappwebview

사용법

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;
    },
  )

0개의 댓글