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​


part는 import다음으로 최상단에 위치해야한다.

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 사용 :::

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

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

StatefulWidget은 ConsumerStatefulWidget으로 바꿔야 한다.

바꾸기 싫으면 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 됐었음