Skip to main content

· 2 min read
Park Ki Hyun

ListView​


ListView 위젯을 Column/Row위젯 아래에 둔다면 ListView와 Column/Row의 사이즈들이 unbounded height가 일어나서 render overflow가 납니다.

Column/Row 아래에 ListView를 두고 싶다면 Expanded 위젯으로 ListView를 감싸야 함.

Notifier Provider​


생성​

notifier를 입력해서 먼저 notifier를 정의한다.

class CounterNotifier extends Notifier<int> {

int build() {
return 0;
}
}

CounterNotifier는 Notifier의 이름이고 Notifier<int>로 돼있는데 int는 반환 타입이다. build함수 앞에는 반환 타입을 붙여줘야 한다.

StateNotifier나 ChangeNotifier를 사용하면 ref를 사용할 수 없지만(사용하려면 생성자에서 정의해줘야 한다.) Notifier를 사용하면 ref를 바로 사용할 수 있다.

tear-offs 지원​

onPressed:(){
func();
}

# 위와 아래는 같다.

onPressed: func,

위 코드와 같은 동작을 하는 것이 tear-offs다.

거추장스러운 부분이 제거가 된다. 함수와 생성자에서 사용할 수 있다.

그래서 아래와 같이 notifierProvider를 선언할 수 있다.

final counterProvider = NotifierProvider<Counter, int>(() {
return Counter();
});

#위를 아래처럼

final counterProvider = NotifierProvider<Counter, int>(Counter.new);

모르면 일단 외우자 tear-offs

접근​

watch하면 state값을 가져올 수 있고 인스턴스에 접근하려면

ref.raed(counterProvider.notifier).increment();

이렇게 할 수 있다.

· One min read
Park Ki Hyun

invalidate​


onPressed: () async {
ref.invalidate(userDetailProvider);
},

refresh할 때 invalidate를 사용한다면 위처럼 onPressed 함수를 구현할 수 있습니다. 물론 skipLoadingOnRefresh도 적용했습니다.

invalidate의 인자값 타입은 ProviderOrFamily이다. 그래서 Family를 주지 않고 적용한다면 모든 provider가 auto dispose된다.

refresh​


onPressed: () async {
return ref.refresh(userDetailProvider(userId));
},

또는

onPressed: () {
return ref.refresh(userDetailProvider(userId).future);
},

refresh 인자 타입은 Family Provider라면 family값을 넘겨주어야 한다. 그리고 return을 사용해야 하기에 return값이 없다면 invalidate를 추천한다.

· One min read
Park Ki Hyun

skipLoadingOnRefresh​


when 메서드에서 skipLoadingOnRefresh라는 메서드가 있는데 true가 디폴트다. 새로고침해도 loading 창 안보여주는건데 false로 해주는 편이 좋다.

새로고침​


ref.refresh() 또는 ref.invalidate()를 사용한다. 예시로는 RefreshIndicator로 했지만 버튼으로 하는게 좋은 UI라고 하는 듯

그리고 윈도우에서는 RefreshIndicator 동작안한다..

freezed class override​



class User with _$User {
const User._();

...

String toString() => 'User(id:$id)';
}

freezed data class 에서 User._()를 선언하고 toString을 다시 오버라이딩 할 수 있던데 이에 대해서 조금 더 알아봐야 할 듯

· 3 min read
Park Ki Hyun

가상 API​


여기 에서 다양한 API를 제공해준다.

users API로 FutureProvider 실습을 한다.

autoDispose 사용 여부​


만약 특정 페이지 진입 시 API를 통해서 값을 가져왔을 때 이 값을 캐싱 할 것이면 autoDispose 사용하면 안되고 다시 안 가져올 것이면 autoDispose 사용하는 것이 좋다.

설정값 같은 경우는 API를 쏴서 변경하는 경우가 거의 없으므로 캐싱 하는 것이 좋다.

future provider body​


when​

값을 불러올 때 when을 사용한 모습입니다.

body: userList.when(
data: (users) {
return ListView.separated(
itemCount: users.length,
separatorBuilder: (context, index) {
return const Divider();
},
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: CircleAvatar(
child: Text(user.id.toString()),
),
title: Text(user.name),
);
},
);
},
error: (e, st) {
return Text(
e.toString(),
style: const TextStyle(fontSize: 20, color: Colors.red),
);
},
loading: () => const Center(
child: CircularProgressIndicator(),
),
),

switch expression​

값을 불러올 때 switch를 사용한 모습입니다.

body: switch (userList) {
AsyncData(value: final users) => ListView.separated(
itemCount: users.length,
separatorBuilder: (context, index) {
return const Divider();
},
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: CircleAvatar(
child: Text(user.id.toString()),
),
title: Text(user.name),
);
},
),
AsyncError(error: final e) => Text(
e.toString(),
style: const TextStyle(fontSize: 20, color: Colors.red),
),
_ => const Center(
child: CircularProgressIndicator(),
),
},

마지막에 원래 _ 대신 AsyncLoading이 들어가야 하지만 무슨 에러가 난다.

userListProvider를 @Sealed로 만들었어야 한다고 한다.

keepalive​


하면서 궁금증이 생겼었는데 값을 가져왔으면 다시 나갔다 왔을 때 loading 안 보고 싶었습니다.

그러면 autoDispose일 때 ref.keepAlive()를 사용하면 됩니다.
주의할 점은 http 호출이 끝나고 적용해야 합니다.

값을 가져온 것을 성공한 페이지는 값이 유지가 되고(dispose안함) error가 나면 dispose 합니다.

· 2 min read
Park Ki Hyun

state provider​


특징으로는 code generator가 안되고

state를 갖고 있다는 점

floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).state++;
},
child: const Icon(Icons.add),
),

위와 같이 read를 하는데 notifier를 사용해서 state에 접근할 수 있다.

그리고 추가로 UI를 그리려면 Future.delay 또는 Listen을 사용할 수 있다.
(그냥 그리려면 에러난다. 충돌나서)

ref.listen<int>(counterProvider, (previous, next) {
if (next == 3) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('counter : $next'),
);
},
);
}
});

요런식으로 listen할 수 있다. 이전 값 다음 값이 있다.

update​


onPressed: () {
ref
.read(autoDisposecounterProvider.notifier)
.update((state) => state + 10);
},

위 코드는

ref.read(autoDisposecounterProvider.notifier).state + 
ref.read(autoDisposecounterProvider.notifier).state + 10

과 같은 코드이다. 너무 길기 때문에 그냥 update 사용하는 것이 편리하다.

state를 인자로 사용할 로직이 필요하면 update 사용하기

Future Provider의 이야기​


remote api를 호출하면 3가지 상태 처리가 필요하다.

in progress, success, error이다.

3가지 모두 동시에 2가지 이상 상태가 될 수 없다.

FutureProvider는 이 3가지 상태에 대한 값을 처리해준다.

AsyncData, AsyncError, AsyncLoading이 있다.

생성자도 3가지 있다.

AsyncValue가 중요하다는 뜻

소소한 팁​


  1. provider 안에 ref.read()를 사용하지 말 것

  2. StateProvider 대신 NotifierProvider 사용할 것

· 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해서 만들 수 없다.(수동만 가능)

· One min read
Park Ki Hyun

mutex와 semaphore 차이​


mutex​

공유된 자원에 스레드들의 동시 접근을 막아줍니다. 1개의 프로세스 혹은 쓰레드만 공유 자원에 접근할 수 있습니다. 키를 기반으로 한 상호배제 기법입니다.

공유된 자원을 카페 화장실로 비유할 수 있고 키를 열쇠로 비유할 수 있습니다.

semaphore​

세마포어도 동시 접근을 막아주는데 1개의 프로세스만 접근할 수 있는 것이 아닌 여러 설정된 최대값 만큼 들어갈 수 있습니다. 빈칸이 0이면 대기해야 합니다.

공유된 자원을 카페 화장실인데 칸이 여러 개 있는 화장실로 비유할 수 있습니다.

· 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

· 2 min read
Park Ki Hyun

Equatable​


dart class generator라는 vs code extension을 사용할 때 객체 비교를 위해서 equatable이라는 dart 패키지를 이용할 수 있습니다.

vs code preferences에서 equatable 관련 설정을 true로 변경한다면 generator를 이용할 수 있습니다.

함수를 통해 객체를 생성하면 주소 값이 달라져서 같은 값을 갖고 있는 객체를 비교해도 false로 나오는데 equatable을 이용해서 비교하면 true를 반환할 수 있습니다.

Json 관련​


json object로 자동 data class가 생성되는 마법이 있다고 합니다.

  1. 일단 dart 파일을 만듭니다.

  2. api 응답 json 값을 넣습니다.

  3. ctrl+shift+p해서 Generate from JSON 입력해서 선택

  4. class 이름 입력

  5. no 선택

그럼 data class 자동 생성 해준다.

fromMap, toMap만 잘 확인해보면 된다. (beta 단계임)