Skip to main content

9 posts tagged with "revierpod"

View All Tags

· 3 min read
Park Ki Hyun

path_provider 종류


gpt의 답변을 정리합니다.

  1. getApplicationDocumentsDirectory()
  • Windows에서 이 함수는 사용자의 "Documents" 폴더에 있는 애플리케이션 데이터 디렉토리를 반환합니다. 보통 C:\Users\<User Name>\Documents 경로로 찾을 수 있습니다.
  1. getApplicationSupportDirectory()
  • C:\Users\User\AppData\Roaming\ [패키지이름]\ [프로젝트이름]
  1. getDownloadsDirectory()
  • 사용자의 "Downloads" 폴더를 반환합니다, 보통 C:\Users\<User Name>\Downloads에 위치합니다.
  1. getExternalCacheDirectories()
  • Windows에서 외부 캐시 디렉토리는 일반적으로 해당되지 않습니다. 이 함수는 Windows에서 지원되지 않을 가능성이 높습니다.
  1. getExternalStorageDirectories({StorageDirectory? type})
  • Windows에서 외부 저장소 디렉토리는 일반적으로 해당되지 않으며, 이 함수는 Android 플랫폼에 특화되어 있습니다.
  1. getExternalStorageDirectory()
  • 이 함수도 마찬가지로 Windows에서는 일반적으로 사용되지 않습니다. Android에서는 외부 저장소의 루트 디렉토리를 반환합니다.
  1. getLibraryDirectory()
  • macOS에서만 사용되며, Windows에서는 해당 디렉토리가 없습니다.
  1. getTemporaryDirectory()
  • 시스템의 임시 폴더를 반환합니다. Windows에서는 보통 C:\Users\<User Name>\AppData\Local\Temp에 위치합니다.

AsyncValeu - properties


value

Async종류previous value [X]previous value [O]
AsyncLoadingnullprevious value
AsyncDatacurrent valuecurrent value
AsyncErrorrethrow errorprevious value

error, stackTrace

Async종류previous error [X]previous error [O]
AsyncLoadingnullprevious error
AsyncDatanullnull
AsyncErrorcurrent errorcurrent error

isLoading/hasValue/hasError

Async종류previous value [X]
previous error [X]
previous value [X]
previous error [O]
previous value [O]
previous error [X]
previous value [O]
previous error [O]
AsyncLoadingisLoading [O]
hasValue [X]
hasError [X]
isLoading [O]
hasValue [O]
hasError [X]
isLoading [O]
hasValue [X]
hasError [O]
isLoading [O]
hasValue [O]
hasError [O]
AsyncDataisLoading [X]
hasValue [O]
hasError [X]
isLoading [X]
hasValue [O]
hasError [X]
isLoading [X]
hasValue [O]
hasError [X]
isLoading [X]
hasValue [O]
hasError [X]
AsyncErrorisLoading [X]
hasValue [X]
hasError [O]
isLoading [X]
hasValue [O]
hasError [O]
isLoading [X]
hasValue [X]
hasError [O]
isLoading [X]
hasValue [O]
hasError [O]

· 6 min read
Park Ki Hyun

AutoDispose


autodispose를 사용하려면

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

로 선언해야 하고

class Counter extends AutoDisposeNotifier<int>{}

위처럼 Notifier를 사용해야 한다.

Family


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

family를 사용하려면 family와 넘겨줄 인자값을 추가해야하고

class Counter extends FamilyNotifier<int, int> {

int build(int arg) {return arg;}
}

Notifier도 FamilyNotifier와 인자 추가와 build함수에 인자를 추가해야 한다.

AutoDispose Family


위의 둘을 합치면 된다.

riverpod generator 이용


generator를 이용해서 notifier를 만들면 더 쉽다.

  1. riverpodpart 만들기

  2. riverpodclass로 만들기 (riverpodclass가 notifier다.)

  3. 이름 넣고 build 함수 타입 지정하기

  4. 나머지 구현

AutoDispose는 어노테이션에 따라 결정되고 family 여부는 build함수 인자값 존재 유무로 결정된다고 합니다.

추가 특이사항이 있으면 그 때 정리해야지.

state shape


2가지 있다고 한다.

enum, sealed .. 그렇다

추가로 AsyncValue도 있다.

이곳의 API를 활용한다.

data model을 freezed로 만드는 절차


  1. ptf

  2. pts

  3. fdataclass

  4. factory 생성자에 인자값 넣기 required로 아니여도되구

  5. fromJson키워드로 fromJson 만들기 (이름은 class이름붙이고)

  6. 메서드들 만들기(empty,add,remove 등)

stat를 freezed로 만드는 절차


  1. ptf

  2. fdataclass

  3. 필요한 factory 메서드 만들기

dio provier 만들기 절차


  1. riverpodpart

  2. riverpod으로 provider만들기

  3. 아래 문법 처럼


Dio dio(DioRef ref) {
return Dio(BaseOptions(baseUrl: '[Target URL]'));
}

Enum based에서 값 요청 provider 만드는 절차


  1. riverpodpartriverpodclass로 초기 설정 후 이름 설정

  2. build 함수 타입 정해주고 return 값 정해주기

  3. 여러 상태 컨트롤하는 함수 만들기(loading, error, success 등등)

  4. 흐름을 적자면 먼저 state의 status를 loading으로 바꿔주고 Get요청을 날린다음에 성공적으로 값을 가져오면 fromJson해가지구 copyWith해서 status success와 값을 가져온 값으로 변경을 한다. 에러가 난다면 copyWith해서 status failure와 error에 e.toString 넣어줌

특이점 정리

  • copyWith로 값 변경
  • single state class임

inistate modifier 주의할 점

페이지에 진입했을 때 바로 api를 쏘는 동작을 하고 싶어서 initState에 api 통신하는 코드를 넣었다. 이 때 UI를 수정하는 동작도 포함돼있어서

FlutterError (Tried to modify a provider while the widget tree was building.... 에러가 났다.

  
void initState() {
super.initState();
ref.read(enumActivityProvider.notifier).fetchActivity(activityTypes[0]);
}

// 이거를 아래로 수정


void initState() {
super.initState();
Future.delayed(Duration.zero, () {
ref.read(enumActivityProvider.notifier).fetchActivity(activityTypes[0]);
});
}

Future.delayed로 감싸면 에러가 해결된다. 이걸 사용하면 비동기적으로 실행하는듯 그래서 UI 그려지기 까지 기다려주는 것 같음.

Sealed class based


특징은 multi class 이다.

절차

  1. sealed class를 만든다.
sealed class StateClass {
const StateClass();
}
  1. StateClass를 extend하여 init, loading, success, fail을 구현한다.
final class StateClassInit extends StateClass {
const StateClassInit();


String toString() => 'StateClass()';
}
  1. provider에서 state 할당할 때 copyWith을 사용하는게 아니라 구현한 class를 사용한다. 나머지는 비슷

async처럼 보이게하는 notifier


enum

setInit 대신에 provider에서 build함수 안에 fetch해주면 된다.

  
StateClass build() {
fetchActivity(types[0]);
return StateClass.init();
}

주의할 점

이 때도 위처럼만 해버리면 에러가난다.

future.delayed로 감싸는 것이 아닌 state에 init()을 먼저 할당해준다.

  
StateClass build() {
state = StateClass.init();
fetchActivity(types[0]);
return StateClass.init();
}

AsyncNotifier


방식은 Notifier와 비슷하지만 안에 생성하는 메서드들은 모두 Future<void>타입이다.

AsyncValue가 좋은게 AsyncLoading에서 이전값을 가지고 있고 AsyncData에서 바뀐 next값을 가지고 있다.

AsyncError에서도 value에는 이전값 보유한다.

guard

Async할 때 try catch문에서 보통 try에 AsyncData, catch에선 AsyncError을 정의합니다. 이를 간소화시켜주는 방법입니다.

state = await AsyncValue.guard(() async {
await wait();
return state.value! + 1;
});

이렇게 하면 알아서 AsyncData와 AsyncError에 값을 넣어준다.

riverpod annotation 절차

  1. riverpodpart

  2. riverpodAsyncClass / 작명 / build함수 타입 지정

  3. build에 async 추가 및 family 할거면 build에 인자 추가

  4. 추가 값들 추가

FutureOr<int> build({required int init}) async {

return init;
}

riverpod annotation 사용하면 named parameter 사용할 수 있다.

skip error

AsyncValuewhen 메서드 사용 시 skipError 속성이 있다.

이 속성은 default가 false고 true로 바꾸면 에러 발생 시 error: 로직에 처리된게 실행되는게 아닌 data가 있다면 이전 데이터를 보여주는 것이다.

· 2 min read
Park Ki Hyun

ListView


ListView 위젯을 Column/Row위젯 아래에 둔다면 ListViewColumn/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함수 앞에는 반환 타입을 붙여줘야 한다.

StateNotifierChangeNotifier를 사용하면 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


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

StateProvider


특징

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