Skip to main content

4 posts tagged with "provider"

View All Tags

· 3 min read
Park Ki Hyun

final helloProvider = Provider<String>((ref) {
ref.onDispose(() {
print("[helloProvider] : disposed");
});
return "Hello";
});

riverpod의 기본 모습이 이렇다.

변수에 할당하고 Provider 부분은 사용할 Provider 종류,

<String>에는 return값 타입을 정의한다.

ref를 통해서 여러 기능을 사용할 수 있는데 ref.onDispose해주면 dispose된다.

onDispose

해당 provider가 dispose되면 실행되는 함수

위의 코드는 자동으로 dispose 되지 않는다.

대표적 3가지

watch : 값 변하는지 계속 확인하고 변하면 리빌드 (async하면안된다.)

listen : 값 변경되면 리빌딩 되는 것이 아닌 액션을 수행(navigate같은) (async하면 안된다.)

read : 값을 확인하지만 변경을 watch하지 않음(build에서 사용하지마라)

auto dispose


final autoDisposeHelloProvider = Provider.autoDispose<String>((ref) {
print('[autoDisposeHelloProvder]: created');
ref.onDispose(() {
print('[autoDisposeHelloProvder]: disposed');
});
return 'Hello';
});

페이지에서 나오면 autodispose해준다.

family


final familyHelloProvider = Provider.family<String, String>((ref, name) {
ref.onDispose(() {
print('[familyHelloProvider] disposed');
});
return 'Hello $name';
});

이렇게 provider 정의하면 argument 사용할 수 있다.

    final helloA = ref.watch(familyHelloProvider('A'));
final helloB = ref.watch(familyHelloProvider('B'));

watch할 때도 이런식으로 해야한다.

autoDisposeFamily


final autoDisposeFamilyHelloProvider =
Provider.autoDispose.family<String, String>((ref, name) {
ref.onDispose(() {
print('[autoDisposeFamilyHelloProvider] disposed');
});
return 'Hello $name';
});

autoDispose와 family를 모두 사용할 수 있다.

만약 둘의 인자값이 A라면 dispose는 한번만 일어납니다. (그냥 autoDispose도 한번만 일어남)

객체 넘기기


class Counter {
final int count;
Counter({
required this.count,
});


String toString() => 'Counter()(count: $count)';
}

final counterProvider = Provider.autoDispose.family<int, Counter>((ref, c) {
ref.onDispose(() {
print('[countProvider($c)] disposed');
});
return c.count;
});

만약 위처럼 객체를 넘겨서

    ref.watch(counterProvider(Counter(count: 0)));
ref.watch(counterProvider(Counter(count: 0)));

이렇게 watch를 조지면 dispose가 두번 일어납니다. 둘 다 다른 객체로 판단합니다.

class Counter extends Equatable {
final int count;
const Counter({
required this.count,
});


String toString() => 'Counter()(count: $count)';


List<Object> get props => [count];
}

Counter 객체를 Equatable해야 dispose 한번만 일어난다.

정리


autoDispose 조심히 사용해야 한다. 계속 dispose 하기 때문에

· 3 min read
Park Ki Hyun

code generator


rivp: riverpodPart

part 'b_provider.g.dart';

riverpodKeepAlive

riverpod code generator는 자동으로 autoDispose인데 keepAlive:true를 해주면 autoDispose하지 않는다.

(keepAlive: true)
String hello(HelloRef ref) {
ref.onDispose(() {
print("[helloProvider] : disposed");
});
return "Hello";
}

String: return의 타입 hello: 나중에 helloProvider라는 이름을 갖는다. HelloRef: generator하면 Ref로 나오는데 hello에서 앞글자를 대문자로 해서 붙이면 HelloRef가 된다. 이걸로 교체해줘야한다.

코드생성

dart run build_runner build -d

위 처럼 build runner 실행해서 generate할 수 있다.

-d옵션은 generate중 충돌이 나도 그냥 생성하라는 뜻입니다.

dart run build_runner watch -d

watch를 사용하면 한번만 사용하면 변화를 계속 watch해서 적용된다.

차이점

수동 생성과 차이점은

  1. 기존은 helloProvider 이렇게 했다면 자동 생성에서는 hello로만 해도 helloProvider 생성해줌.

  2. 자동생성에서는 Provider 종류 지정 안해도된다.

Provider


autoDispose


autoDispose Provider는 riverpod스니팻 으로 만들 수 있습니다.


String autoDisposeHello(AutoDisposeHelloRef ref) {
print('[autoDisposeHelloProvder]: created');
ref.onDispose(() {
print('[autoDisposeHelloProvder]: disposed');
});
return 'Hello';
}

family


family는 그냥 riverpod이나 riverpodkeepalive에서 ref 옆에 파라미터 추가해주면 family로 자동으로 만들어줍니다.

(keepAlive: true)
String familyHello(FamilyHelloRef ref, {required String name}) {
ref.onDispose(() {
print('[familyHelloProvider] disposed');
});
return 'Hello $name';
}

참고로 name을 사용하면 에러난다. 그 이유는 generator에서 name이라는 변수를 이미 사용하고 있기 때문이다.

autoDispose, family



String autoDisposeFamilyHello(AutoDisposeFamilyHelloRef ref,
{required String nom}) {
ref.onDispose(() {
print('[autoDisposeFamilyHelloProvider] disposed');
});
return 'Hello $nom';
}

별로 어렵지 않음

한가지 배운 dart 언어 syntax


partimport다음으로 최상단에 위치해야한다.

StateProvider


특징

  • stateProvider는 notifierProvider의 간소화된 버전
  • 복잡하지 않는 로직을 쓸 때 사용
  • code generator해서 만들 수 없다.(수동만 가능)

· 3 min read
Park Ki Hyun

factory Constructor


factory constructor의 constructor redirection에 대해 알고 있어야 한다. 그리고 const를 사용할 수 있음.

필요한 패키지


freezed_annotation: dep : 코드 생성기에 대한 어노테이션 패키지

build_runner : dev-dep : 코드 생성기 실행

freezed : dev-dep : 코드 생성기

json_annotation : dep : 코드 생성기인 json_serializable의 어노테이션 갖고 있는 패키지

json_serializable : dev-dep : 코드 생성기

freezed와 json_serializable 사용할 경우

analysis_options.yaml에 설정

analyzer:
errors:
invalid_annotation_target: ignore
exclude:
- '**/*.freezed.dart'
- '**/*.g.dart'

build runner를 사용해서 코드 gen하려면

dart run build_runner build [--delete-conflicting-outputs]

or

dart run build_runner watch [-d]

build는 one-time, watch는 계속 빌드함

watch가 빌드 타임 적다.

[]에 있는 옵션은 previous build에서 발생한 것 건너뛰기

extension

freezed라는 확장 설치해야 한다.

ptf : part '.freezed.dart'; 생성 pts : part '.g.dart'; 생성 fdataclass : Dataclass 생성 funion : Union class 생성

기능


정리

우선 다양한 것들을 override해준다.

toString이나 copyWith, equality 등을 오버라이딩 해줌.

copyWith

일반적인 copyWith + null도 지원해줌

deep copyWith도 제공해준다. 복잡한 코드를 간단하게 작성할 수 있도록 지원

Json annotation

(name: 'parking_lot_capacity') int? parkingLotcapacity,

data class를 만들 때 이런 인자값을 볼 수 있다.

변수 parkingLotcapacity를 json키 값 'parking_lot_capacity'로 판단하겠다는 뜻이다.

  factory Hotel.fromJson(Map<String, dynamic> json) => _$HotelFromJson(json);

이런 것도 제공해준다.

Provider


위젯이나 다른 Provider에 값을 제공

riverpod에서는 Provider를 watch할 수 있다. 그리고 그냥 값을 제공하는 Provider다.

dep

equatable,flutter_riverpod,riverpod_annotation

dev-dep

build_runner,custom_lint,flutter_lints,riverpod_generator, riverpod_lint

· 7 min read
Park Ki Hyun

const에 대해


class에서 생성자를 const로 만들지 않았을 때

class Product {
final int id;
final String name;
final String desc;
final String price;
final String imageUrl;

Product({
required this.id,
required this.name,
required this.desc,
required this.price,
required this.imageUrl,
});
}

위처럼 만들면 아래 코드는 에러난다.

const List<Product> storeProductList = [];

생성자 Product에 const를 붙여주면 해결할 수 있다.

context.read에 대해


void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => MyCart(),
),
ChangeNotifierProvider(
create: (context) => MyBadge(myCart: context.read()),
),
],
child: MaterialApp(
home: MyApp(),
),
),
);
}

코드 상황이 위와 같을 때 어떻게 MyCart의 정보를 contex.read()로 가져오나 의문이 있었다.

본론부터 말하면

        ChangeNotifierProvider(
create: (context) => MyBadge(myCart: context.read<MyCart>()),
),

위와 같이 사용하면 헷갈릴 일이 없고 가져올 수 있는 이유는 ChangeNotifierProvider를 통해서 MyCart를 위젯 트리 상 등록 했기에 바로 가져와지는 것이다. 만약 중간에 MyStore 같은 다른 위젯이 있었다면 MyCart는 저런식으로 가져오지 못한다.

List의 값에 const에 대해


List<Product> cartProductList = const [];

리스트의 불변을 보장해주기 위해 []앞에 const 키워드를 붙일 수 있다. List<Product> 앞에 붙이는 것과는 다르다. 리스트를 바꿀 수 있지만(요소 말고) 리스트에 직접적인 값을추가하거나 삭제 수정은 못한다.

삭제를 원한다면 where를 사용할 수 있다.

list.where((p)=>p!= product).toList();

위처럼 작성하면 list를 돌면서 돌고 있는 값이 p인데 product와 비교하면서 product는 빼고 다시 리스트를 만드는 코드이다.

추가를 원한다면

[...list,product]

아래처럼 간단하게 ...연산자를 사용할 수 있다.

dart 자동 매개변수..?


  void onProductPressed(Product product) {
if (cartProductList.contains(product)) {
cartProductList = cartProductList.where((p) => p != product).toList();
} else {
cartProductList = [...cartProductList, product];
}
notifyListeners();
}

이렇게 매개변수가 필요한 함수로 구현돼있는데

      return ProductTile(
product: product,
isInCart: providerCart.cartProductList.contains(product),
onPressed: providerCart.onProductPressed,
);

이렇게 사용하길래 깜짝 놀랐다. 뭔가 싶었는데 ProductTile class에 비밀이 있었다.

  final Product product;
final bool isInCart;
final void Function(Product product) onPressed;

이렇게 onPressed가 구현돼있으면 product를 넣지 않아도 알아서 넣어준다고..

product를 넣지 않았는데 어떻게 아냐구요? ProductTile 위젯을 만들 때 product값을 전달하기 때문에 인지할 수 있다고 하네요 !

정리하자면 특정 위젯에 a라는 값을 통해서 구현하고 그 a라는 값을 매개변수로 사용하는 함수가 있을 때 다른 함수도 그 a라는 값을 필요로 한다면 사용하지 않는다.. 라는 것입니다. 근데 100% 이해를 못했다. 마음에 담아두자.

context.read() 2


한 가지를 더 이해해 버렸다.

위젯 트리 상에 등록해서 context로 가져올 수 있다면

context.read<ProviderCart>().onProductPressed

위 처럼 ProviderCart의 함수를 가져올 수 있다.

context.select()


이거 좀 이상하긴 하다. 그래서 이제 완벽히 이해했다.

context.watch() : Provider의 모든 상태를 구독한다.

context.select(): Provider의 특정 상태를 구독한다.

final count = context.select<Counter,int>((counter) => counter.count);

위 처럼 사용하기 때문에 select는 특정 상태 구독 가능

context.read(): 구독안하고 현재 상태 그냥 가져오기

드디어 context 3형제는 이해한듯

Riverpod


provider 만든 사람이 만든거라고 해서 provider랑 비슷할 줄 알았는데 다르다.

provider 등록을 MultiProvider에 등록하는 것이 아닌 그냥 전역 변수로 등록한다. 별도 변수에 할당하기 때문에 같은 Provider를 다른 변수에 할당할 수 있다. 그리고 BuildContex를 사용하지 않고 별도의 WidgetRef라는 것을 사용한다.

변경점 정리
  1. provider를 전역 변수로 선언해서 사용
  2. BuildContext 대신 WidgetRef 사용 :::

나머지는 차차 알아가보는걸로

StatelessWidgetConsumerWidget으로 바꿔야한다. 그리고 build()메소드 파라미터에 WidgetRef ref를 추가해야 ref사용할 수 있다.

StatefulWidgetConsumerStatefulWidget으로 바꿔야 한다.

바꾸기 싫으면 Consumer 위젯으로 감싸서 사용할 수 있다.

provider 종류

  1. StateProvider : 별로 클래스 선언 없이 가볍게 사용할 수 있다.

  2. NotifierProvider : 클래스를 만들어서 사용할 수 있습니다.

  3. Provider : 기존 Provider의 Provider와 같음 즉 상태가 없는 Provider들

잡기술

  1. AutoDispose : NotifierProvider뒤에 .autoDispose를 붙이고 Notifier extends한 것을 AutoDisposeNotifier로 수정하면 됩니다. 자동으로 dispose 해줍니다.

  2. family : NotifierProvider뒤에 family를 추가해서 사용하면 되느데 매개변수 타입을 추가해주면 build()에 매개변수 추가해서 사용할 수 있습니다.

  3. AutoDisposeFamilyNotifier : 1,2번을 합친 것.

Error: Unable to find git in your PATH.


간혹 flutter를 처음할 때 아래와 같은 에러가 괴롭힌다.

Error: Unable to find git in your PATH.
git config --global --add safe.directory '*'

위 명령어로 해결할 수 있음 저장소 권한 문제인 것같다. 관리자 powershell은 flutter 됐었음