Riverpod v2ã®AsyncValueãçè§£ãã
CHANGELOG
- 2022.11.23
-
v2.1.0ã«è¿œåŸãé¢é£ç®æãä¿®æ£ -
copyWithPreviousã®å¯Ÿè±¡ãšãªã Provider ãæèš - copyWithPreviousã®æåã现ãããã³ããªã³ã°ãã ã»ã¯ã·ã§ã³ã远å
-
- 2022.11.06
-
v2.1.0ã®ãªãªãŒã¹ã«äŒŽãv2.0.xãå ã«å·çããŠããèšäºã§ããæšãæèš
-
- 2022.10.30
- 9ææ«ã® v2.0.0å®å®çæäŸã«äŒŽãé¢é£ç®æãäžéšä¿®æ£
- ãšãã«
.future.streamã®åé€ã«ããé¢é£ã»ã¯ã·ã§ã³ã®åé€ -
Provider<AsyncValue<T>>ã䜿ããFutureProviderStreamProviderããã®ãŸãŸè¿åŽããæžãæ¹ã«å€æŽ
ã¯ããã«
Riverpodãéåžžã«äŸ¿å©ã§åŒ·åãªç¶æ
管çããã±ãŒãžã§ããã
ç§ãæåã«è§Šãå§ããã®ã¯çŽ2幎åé ã® v0.6.0 ã§ããããããã v1ã®ãªãªãŒã¹ã»v2ã®ãã¬ãªãªãŒã¹ãšçã
ãšé²åãéããŠããã®ãåãããŸãã
ãããªæçšããã±ãŒãžã§ãããããã¥ã¡ã³ã èªäœã¯çšæãããŠãããã®ã®ïŒæ°ã¶æåæ¥æ¬èªèš³ãæäŸ ãããŸãããðïŒããã®å
容㯠Provider ã®çš®é¡ã䜿ãåãã修食è©ã«çãŸã£ãŠãããå°ããŸã ç©è¶³ããªããæããŸããFlutter ã®ããã¥ã¡ã³ããæåããããããã«çžå¯Ÿçã«ããèŠããŠããŸã£ãŠããã ããããããŸããã
ä»åã¯ãäžèšã®ããã¥ã¡ã³ãã«ã¯ãããŸã§æåãèšèŒãããŠã¯ããªããã®ã®ãRiverpod ãæ±ãäžã§éèŠãª AsyncValue ã«ã€ããŠè§ŠããŠãããŸãã
AsyncValue 㯠FutureProvider/StreamProvider/AsyncNotifierProvider ãæ±ãéã«æ¬ ãããªãã¯ã©ã¹ã§ãRiverpod ã®äœè
ãããã Provider ã«ã¯é·æçã«å€§ããªèšç»ãç«ãŠãŠãããšèšåããŠãããçã
å©çšæ©äŒã¯å¢ããŠããã®ã ãšæããŸãã
And to be fair, I have a more long-term vision of Riverpod.
I have big plans for FutureProvider, while StateNotifierProvider is going to become less useful over time.
https://twitter.com/remi_rousselet/status/1537563116220952578
察象ã®èªè
- Riverpod ã®ã·ã³ã¿ãã¯ã¹ã Provider ã®çš®é¡ãªã©åºæ¬çãªããšãããæ¹
- StateNotifierProvider ã§ MVVM çã«å®è£ ããŠãããæ©èœåäœã§åãé¢ããããšèããŠããæ¹
- AsyncValue ã®äœ¿ãæ¹ãæ¯ãèãã®çè§£ã«èªä¿¡ããªãæ¹
AsyncValueã¯ã©ã¹
æ¹ããŠãAsyncValue 㯠Future ã Stream ãªã©ã®éåæåŠçã§çºçãã loading/error ç¶æ ãç°¡åã«ãã³ããªã³ã°ã§ããã¯ã©ã¹ã§ãã
ãœãŒã¹ã³ãŒãäžã¯ common.dart ã«éçŽãããŠããŸãã
ã³ãŒãã®éããAsyncValue 㯠sealed ã¯ã©ã¹ãšãªã£ãŠãããdataã»loadingã»error ã®3ã€ã factory ã§å®çŸ©ãããŠããŸãã
const factory AsyncValue.data(T value) = AsyncData<T>;
const factory AsyncValue.loading() = AsyncLoading<T>;
const factory AsyncValue.error(Object error, {StackTrace? stackTrace}) = AsyncError<T>;
ä»ã«ããtry~catch ãçã guard() ã¡ãœããããisLoading ããããã£ãªã©ãçšæãããŠããŸãã
çŽåã®ç¶æ ãç¶æããcopyWithPreviousã¡ãœãã
AsyncValue ã¯3ã€ã®ç¶æ ãæã¡ãŸããã以äžã® Provider ã®ã¿ç¶æ ãå€åããéã«ããã©ã«ãã§çŽåã®å€ãç¶æããæ§è³ªããããŸããããããéåæåŠçãæ±ã Provider ã§ãã
- FutureProvider
- StreamProvider
- AsyncNotifeirProvider
å
ã
ãStateNotifierProvider ã copyWithPrevious ã®æ§è³ªãæã£ãŠããã®ã§ãããv2ããäžèš3ã€ã«éå®ãããŸãããåºæ¬çã«ãã®æ§è³ªã¯ãéåæåŠçã«ããããŠãŒã¶ãŒã€ã³ã¿ãã§ãŒã¹ãåäžãããç¹ã«å¹æãçºæ®ããããããã®åœ¹å²ãæç¢ºã«ããã®ã ãšæããŸãã
FutureProvider, StreamProvider and AsyncNotifierProvider now preserve the previous data/error when going back to loading. This is done by allowing AsyncLoading to optionally contain a value/error.
https://pub.dev/packages/riverpod/changelog#210
ããšãã°ãååã§ã®ããŒã¿ååŸãæåããåŸãäžæçãªãµãŒããšã©ãŒãªã©ã§ãã®åŸã®ååŸã«å€±æããå Žåã«ã¯ AsyncError ãè¿ã£ãŠããŸãããååã§ååŸããããŒã¿ã¯ãã®ãŸãŸä¿æããŠããŸããããã«ãããŠãŒã¶ãŒã¯ãšã©ãŒãçºçããŠããŸã£ãå Žåã§ããçŽåã®ã³ã³ãã³ãã¯ãã®ãŸãŸé²èЧã§ããå©çšäœéšãç¶æã§ããŸãïŒãšã©ãŒå
容ãå
šé¢çã«ãŠãŒã¶ãŒã«äŒããããšãã§ããéçºè
åŽã§èªç±ã«ãã³ããªã³ã°ãå¯èœã§ãïŒã
ãããå®çŸããŠããã®ã copyWithPrevious ã¡ãœããã§ãåç¶æ¿å
ã¯ã©ã¹ã§ override ãããŠããã®ãåãããŸãã
詳现ã¯ãœãŒã¹ã³ãŒããèŠããšè¯ããšæããŸããããŸãšãããšä»¥äžã®è¡šãšãªããŸãã
泚æãªã®ã¯ãAsyncLoading ã¯ã©ã¹ã¯ååèªã¿èŸŒã¿æã«ããè¿åŽãããªããšããç¹ã§ãã
| ã¯ã©ã¹ | æå |
|---|---|
AsyncData |
åã®ç¶æ
ã«é¢ä¿ãªãææ°ã® AsyncData ã§äžæžã |
AsyncLoading |
åã®ç¶æ
ã« isLoading: true ã«å€ããŠè¿åŽãååèªã¿èŸŒã¿æã®ã¿ AsyncLoading ã¯ã©ã¹ãè¿ãã |
AsyncError |
ææ°ã®ãšã©ãŒæ å ±ã¯è¿ãã€ã€åã®ç¶æ ã«ããŒã¿ãããã°ç¶æ |
ãã¡ãã«ãœãŒã¹ã³ãŒããæç²ããŠèšèŒããŸããã
copyWithPrevious ã®è©²åœãœãŒã¹ã³ãŒã
class AsyncData<T> extends AsyncValue<T> {
// åã®ç¶æ
ïŒpreviousïŒã«é¢ä¿ãªãåçç¡çšã§AsyncDataã§äžæžãããŠããããšãåãããŸãã
// æ£åžžã«ããŒã¿ååŸãã§ããå Žåã¯çŽåã®ãšã©ãŒæ
å ±ãªã©ã¯äžèŠãšããç¹ã§çŽåŸã§ãã
@override
AsyncData<T> copyWithPrevious(AsyncValue<T> previous) {
return this;
}
}
class AsyncLoading<T> extends AsyncValue<T> {
// AsyncLoadingã¯åã®ç¶æ
ã«`isLoading: true`ãä»äžããŠè¿åŽããŠããããšãããããŸãã
// åã®ç¶æ
ãæåããŠããå Žåã¯`AsyncData(isLoading: true)`
// åã®ç¶æ
ã倱æããŠããå Žåã¯`AsyncError(isLoading: true)`
// ååèªã¿èŸŒã¿æã®ã¿AsyncLoadingãè¿ããŸãã
@override
AsyncValue<T> copyWithPrevious(AsyncValue<T> previous) {
return previous.map(
data: (d) => AsyncData._(
d.value,
isLoading: true,
error: d.error,
stackTrace: d.stackTrace,
),
error: (e) => AsyncError._(
e.error,
isLoading: true,
value: e.valueOrNull,
stackTrace: e.stackTrace,
hasValue: e.hasValue,
),
loading: (_) => this,
);
}
}
class AsyncError<T> extends AsyncValue<T> {
// ææ°ã®ãšã©ãŒå
容ãã¹ã¿ãã¯ãã¬ãŒã¹ã¯è¿ãã€ã€ã
// åã®å€ã®ååšæç¡ããã®ããŒã¿ãããå Žåã¯ä¿æããŠããããšãããããŸãã
// ããã«ããAsyncErrorã§ãpreviousã®ããŒã¿ã衚瀺ããããšãå¯èœã«ãªã£ãŠããŸãã
@override
AsyncError<T> copyWithPrevious(AsyncValue<T> previous) {
return AsyncError._(
error,
stackTrace: stackTrace,
isLoading: isLoading,
value: previous.valueOrNull,
hasValue: previous.hasValue,
);
}
}
isRefreshing
2.0.0-dev.0 ããå°å ¥ãããŠããæ©èœã§ãåå衚瀺åŸã®ããŒã¿æŽæ°ã®éã«ãèªã¿èŸŒã¿äžã§ãåã®ããŒã¿ããšã©ãŒã UI ãšããŠãã®ãŸãŸè¡šç€ºã§ããŸãã
After a provider has emitted an AsyncValue.data or AsyncValue.error, that provider will no longer emit an AsyncValue.loading.Instead, it will re-emit the latest value, but with the property AsyncValue.isRefreshing to true.
å®è£
ã¯åçŽã§ãAsyncData ã AsyncError ã®ãvalue ã error ãååšããç¶æ
ãããåèªã¿èŸŒã¿ããæã« true ãšè©äŸ¡ãããããšãåãããŸãã
bool get isRefreshing {
return isLoading && (hasValue || hasError);
}
Riverpod ã®ãªããžããªã« isRefreshing ã®ãã¹ã ãããã®ã§ããããå
ã«æåãçè§£ã§ããŸããåå€å®ã®éšåã®ã¿è¿œå ããŠãããŸãã
// ref. https://github.com/rrousselGit/riverpod/blob/2ed9fa0091ff1d0c3d0d37d89a3d5de06c8105d7/packages/riverpod/test/framework/async_value_test.dart#L76
test('isRefreshing', () {
// AsyncLoading
expect(const AsyncLoading<int>().isRefreshing, false);
final previousIsLoading = const AsyncLoading<int>().copyWithPrevious(
const AsyncLoading(),
);
expect(previousIsLoading.isRefreshing, false);
expect(previousIsLoading, isA<AsyncLoading<int>>());
// AsyncData
expect(const AsyncData<int>(42).isRefreshing, false);
final previousIsData = const AsyncLoading<int>().copyWithPrevious(
const AsyncData<int>(42),
);
expect(previousIsData.isRefreshing, true);
expect(previousIsData, isA<AsyncData<int>>());
// AsyncError
expect(const AsyncError<int>('err').isRefreshing, false);
final previousIsError = const AsyncLoading<int>().copyWithPrevious(
const AsyncError<int>('err'),
);
expect(previousIsError.isRefreshing, true);
expect(previousIsError, isA<AsyncError<int>>());
});
å ·äœäŸ
ããããäžè¬ç㪠API ã§ã®ããŒã¿ååŸã®åããšããŠããååã®ããŒã¿ååŸåŸã«ãªãã¬ãã·ã¥ããå Žåã®æåãã以äžã®å³ã«ãŸãšããŸããã

èŠç¹ã¯ãïŒ3ïŒãªãã¬ãã·ã¥ãæã« AsyncLoading ã§ã¯ãªã AsyncData(isLoading: true)ãšãªã£ãŠããç¹ã§ããåè¿°ã® copyWithPrevious ã§ãè¿°ã¹ãŸããããAsyncLoading ã® copyWithPrevious ã¯ãåã®ç¶æ
ã« isLoading: true ãšããŠè¿åŽãã®æåãšãªãããããããã£ãã±ãŒã¹ã§ isRefereshing: true ãšãªããŸããAsyncError ãããªãã¬ãã·ã¥ããæãã»ãŒäžç·ã®æåãšãªããŸãã
test('ååã®ããŒã¿ååŸåŸã«ãªãã¬ãã·ã¥ããå Žå', () {
// â ååèªã¿èŸŒã¿
const loading = AsyncLoading<int>();
expect(loading.isLoading, isTrue);
expect(loading.isRefreshing, isFalse, reason: 'previousããªãã®ã§false');
expect(loading, isA<AsyncLoading<int>>());
// â¡ããŒã¿ååŸæå
final initialValue = const AsyncData(1).copyWithPrevious(loading);
expect(initialValue.value, 1);
expect(initialValue.hasValue, isTrue);
expect(initialValue.isLoading, isFalse);
expect(initialValue.isRefreshing, isFalse);
expect(initialValue, isA<AsyncData<int>>());
// â¢ãªãã¬ãã·ã¥ïŒPull-to-Refreshãªã©ã§æŽæ°ïŒ
final refreshing = const AsyncLoading<int>().copyWithPrevious(initialValue);
expect(refreshing.hasValue, isTrue, reason: 'initialValueã®1ãããã®ã§true');
expect(refreshing.isLoading, isTrue);
expect(refreshing.isRefreshing, isTrue, reason: 'hasValueãtrueãªã®ã§true');
expect(refreshing, isA<AsyncData<int>>(), reason: 'previousãAsyncDataã®ãã');
// â£æŽæ°å®äº
final value = const AsyncData<int>(2).copyWithPrevious(refreshing);
expect(value.value, 2);
expect(value.hasValue, isTrue);
expect(value.isLoading, isFalse);
expect(value.isRefreshing, isFalse);
expect(value, isA<AsyncData<int>>());
});
ããã«ãã£ãŠãŠãŒã¶ãŒç®ç·ã§ã¯ãïŒ2ïŒã§ååŸããå€ã衚瀺ããããŸãŸãè£ã§ïŒ3ïŒã®ãªãã¬ãã·ã¥åŠçãè¡ãããïŒ4ïŒã®ååŸå®äºãšåæã«ããããšå€ãåãæ¿ãã圢ã«ãªããŸãã
éã«ããªãã¬ãã·ã¥ãšååèªã¿èŸŒã¿ã® UI ãçµ±äžãããïŒex. èªèŸŒäžã¯åžžã«ã€ã³ãžã±ãŒã¿ã衚瀺ããããïŒãšãã£ãå Žåã«ã¯ãAsyncData,AsyncError ã§ã isLoading: true ãšãªãã®ã§ãæ®éã« isLoading å€å®ã ãããã°è¯ãã§ããã
å€ã®åãåºããšäŸ¿å©ãªã·ã³ã¿ãã¯ã¹
AsyncValue ã«ã¯ä»ã«ãããŸããŸãªã¡ãœãããæ¡åŒµãçšæãããŠããŸããåè¿°ãã isRefereshing ã extension ã§çšæãããŠãã getter ã®1ã€ã§ãããã¹ãã³ãŒãã async_value_test.dart ã«æžãããŠããã®ã§ãã¡ããåãããŠã¿ãã®ããªã¹ã¹ã¡ã§ããä»åã¯ãã®äžã§ãå人çã«ãšãã«ãã䜿ããã®ãããã¯ã¢ããããŠèšèŒããŸãã
whenã»whenOrNullã»mayBeWhen
ãã£ãšã銎æã¿ã®ãã extension ã when ã ãšæããŸããä»ã®èšäºã§ãããããèšåãããŠãããŸãããdata/loading/error ã®3ç¶æ
ã«å¿ããŠè¡šç€ºãåãæ¿ããããšãã§ããŸãã
.when ã®ç¹åŸŽ
-
AsyncValueãåãåºãéã®ãã£ãšãã¹ã¿ã³ããŒãïŒäž»èгïŒãªæžãæ¹ - 3ç¶æ ã«å¿ããŠè¡šç€ºãåãæ¿ããããšãã§ãã
- äžå¯§ãªãã³ããªã³ã°ãäžèŠãªå Žé¢ã§ã¯ããåé·ãšãªãåŽé¢ããã
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(someValueProvider).when(
loading: CircularProgressIndicator.new,
error: (error, stacktrace) => Text(error.toString()),
data: (value) {
// AsyncDataã®å€
return Text('$value');
},
);
}
åè¿°ã® copyWithPrevious ã®éãã2åç®ä»¥éã®èªã¿èŸŒã¿ã¯ AsyncLoading ã¯ã©ã¹ã«ã¯ãªããªãã®ã§ãäžèšã®å®è£
ã§ã¯ã€ã³ãžã±ãŒã¿ã¯è¡šç€ºãããŸãããååã»2åç®ä»¥éé¢ä¿ãªãã€ã³ãžã±ãŒã¿ã衚瀺ãããå Žåã¯ãisRefreshingïŒisLoading ã§ãåæ§ïŒã®åå²ã远å ããŠãããå¿
èŠããããŸããã
ã€ãã§ã« whenOrNull ãš mayBeWhen ã玹ä»ããŸããååãããåãããŸãããwhen ãåºæºã«ãŠãŒã¹ã±ãŒã¹ã«ãã£ãŠåãæ±ããããããã extension ãšãªã£ãŠããŸãã
.mayBeWhen ã®ç¹åŸŽ
- ãã³ããªã³ã°ãããç¶æ ã®ã¿ã®å®è£ ã§æžã
- ä»ã®ç¶æ
ã¯
orElse()ã§ãŸãšããããšãã§ãã
error ç¶æ
ããšãã«èæ
®ããå¿
èŠãªãå Žé¢ãªã©ã§å©çšã§ããŸãããåŸè¿°ã® .value ã§æžãã±ãŒã¹ãå€ãã®ã§ããŸãåºçªã¯ãããŸããã
ref.watch(someValueProvider).maybeWhen(
// loading/erroræã«ã¯ã€ã³ãžã±ãŒã¿ã衚瀺ããã
orElse: CircularProgressIndicator.new,
data: (value) {
return Text('$value');
},
);
.whenOrNull ã®ç¹åŸŽ
-
.mayBeWhenã®orElse()ã®ä»£ããã«nullãè¿ã
ref.watch(someValueProvider).maybeWhen(
// loading/erroræã«ã¯null
data: (value) {
return Text('$value');
},
);
whenData
whenData ã¯ãAsyncData ãå å·¥ããåŸã« AsyncValue ãè¿åŽããåããããŸãã
ãã®ãããäžèšã® when ç³»ã®ã¡ãœãããšã¯ç°ãªã build ã¡ãœããã§ Widgetããã®ãŸãŸè¿åŽã§ãããProvider å
ã§ AsyncData ãå å·¥ããçšéã§çšæãããŠããã®ã ãšæããŸãã
.whenData ã®ç¹åŸŽ
- å¿
èŠã«å¿ããŠ
AsyncDataã®ã¿å å·¥ãããå Žåã«å©çš - å å·¥åŠçã§ exception ã throw ããå Žåã¯
AsyncErrorãšãªã£ãŠè¿åŽããã -
AsyncLoadingãAsyncErrorã¯whenDataã®å å·¥åŸã®åã§è¿åŽããã- åãå€ãããªãå Žåã¯èŠãç®äžãã®ãŸãŸè¿åŽãããããã«èŠãã
whenData ã®è©²åœãœãŒã¹ã³ãŒã
/// Shorthand for [when] to handle only the `data` case.
///
/// For loading/error cases, creates a new [AsyncValue] with the corresponding
/// generic type while preserving the error/stacktrace.
AsyncValue<R> whenData<R>(R Function(T value) cb) {
return map(
data: (d) {
try {
return AsyncData._(
cb(d.value),
isLoading: d.isLoading,
error: d.error,
stackTrace: d.stackTrace,
);
} catch (err, stack) {
return AsyncError._(
err,
stackTrace: stack,
isLoading: d.isLoading,
value: null,
hasValue: false,
);
}
},
error: (e) => AsyncError._(
e.error,
stackTrace: e.stackTrace,
isLoading: e.isLoading,
value: null,
hasValue: false,
),
loading: (l) => AsyncLoading<R>(),
);
}
whenData ã®æåã®ãã¹ãã³ãŒã
test('transforms data if any', () {
expect(
const AsyncValue.data(42).whenData((value) => '$value'),
const AsyncData<String>('42'),
);
expect(
const AsyncLoading<int>().whenData((value) => '$value'),
const AsyncLoading<String>(),
);
expect(
const AsyncError<int>(21).whenData((value) => '$value'),
const AsyncError<String>(21),
);
});
test('catches errors in data transformer and return AsyncError', () {
expect(
const AsyncValue.data(42).whenData<int>(
// ignore: only_throw_errors
(value) => throw '42',
),
isA<AsyncError<int>>()
.having((e) => e.error, 'error', '42')
.having((e) => e.stackTrace, 'stackTrace', isNotNull),
);
});
whenData ã䜿ã£ãŠå€ã®å å·¥ã»åã®å€æŽãã Provider ã®äŸã以äžã«ãªããŸããæçµçã« WidgetåŽã§åãæ±ããããããã«å å·¥ãã Provider ãæããšãUI åŽã«ããžãã¯ãä»åšããã¹ãããªããŸããã
final countProvider = StreamProvider<int>((ref) => Stream.value(42));
// v2.0.0-dev.9 以å
// `whenData`ã䜿ã£ãŠ`Provider<AsyncValue<T>>`ã«å€æããŠè¿ãæžãæ¹ãããŠãã
final doubleCountLabelProvider = Provider<AsyncValue<String>>((ref) {
return ref.watch(countProvider).whenData((count) => '${count * 2}');
});
// v2.0.0 以é
// `.future`,`.stream`ã廿¢ãããããšã§`Provider<AsyncValue<T>>`ã
// StreamProvider/FutureProviderã®å£åçã«ãªã£ãŠããŸãå©çšäŸ¡å€ãç¡ããªã£ããããã®ãŸãŸå€æãã
final doubleCountLabelProvider = StreamProvider<String>((ref) {
return ref.watch(countProvider.stream).map((count) => '${count * 2}');
});
// çç¥...
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(doubleCountLabelProvider).when(
data: Text.new, // -> 84
error: (error, _) => Text(error.toString()),
loading: CircularProgressIndicator.new,
);
}
valueã»valueOrNull
åè¿°ã® when ç³»ã¡ãœããã¯ç¶æ
ã«å¿ããŠäžå¯§ã«è¡šç€ºã®åãåããã§ããäžæ¹ã§ããããŸã§äžå¯§ã«åå²ãå¿
èŠã§ã¯ãªãã±ãŒã¹ããããŸããããšãã° Firestore ã®ãªã¢ã«ã¿ã€ã ãªã¹ããŒã§ã®ååŸã§ã¯ãç¬æã«ããŒã¿ãæµããŠããããã€ã³ãžã±ãŒã¿ã衚瀺ãããŸã§ããããŸããããŸããåªç§ãªãã£ã·ã¥æ©æ§ã«ãã£ãŠãšã©ãŒãšãªãã±ãŒã¹ããããŸããïŒãããšããã° index ã®è²Œãå¿ããã»ãã¥ãªãã£ã«ãŒã«ã§åŒŸããããªã©ã§ãããããããéçºæ®µéã§æ°ã¥ãããã®ã§ããã®ããã«åå²ãããã®ã¯åã«åé·ãªã³ãŒãã«ãªã£ãŠããŸããŸãïŒããããã¯ãåã« build ã¡ãœããã§ã®ãã¹ããããã¯ãæ°ã«ãªãéçºè
ããããããããŸããã
value ã valueOrNull 㯠AsyncValue ããçŽæ¥å€ãåç
§ã§ãããã®ã§ãäžèšã®ãããªåç¶æ
ã®ãã³ããªã³ã°ãäžèŠãªã±ãŒã¹ã§äœ¿çšããŸãã
.value ã®ç¹åŸŽ
- AsyncLoading ã§ã¯
nullãè¿ã - AsyncError ã§ã¯ exception ã throw ããã
- åè©äŸ¡æïŒisRefreshing: trueïŒã®æã« exception ãçºçããå Žåã¯åã§ååŸããå€ãè¿ã
errorç¶æ ã§ã¯exceptionãthrowããã
ãã¡ãã¯å
ã
throw ãããã« value ãè¿ã£ãŠãã仿§ã ã£ãã®ã§ããããã¡ãã®ã€ã·ã¥ãŒ ã§è°è«ããã2.0.0-dev.6 ã§ãååèªã¿èŸŒã¿æã®ã¿ exception ã throw ãããããã«ãªããŸããã
2åç®ä»¥éã®èªã¿èŸŒã¿æã¯ããšã©ãŒãçºçããã®ãäžèŠæ°ã¥ãã«ããåé¡ããããŸããã倧æµã®ã±ãŒã¹ã§äºæãã¬ãšã©ãŒãçããã®ã¯ååèªã¿èŸŒã¿æãšã³ã¡ã³ããããŠããéãã§çŽåŸã§ããããã®å€æŽã«äŒŽããæ¢åã® AsyncError æã«ã value ãåžžã«è¿ã extension ãšã㊠valueOrNull ã远å ãããŸããããã®ããã.valueOrNull 㯠.value ãšæ¯ã¹ãŠä»¥äžã®éããããã®ã¿ã§ãã
.valueOrNull ã®ç¹åŸŽ
- åºæ¬çã«ã¯
.valueãšã»ãŒäžç· -
.valueãšã®éã㯠AsyncError ã§ãåžžã«valueãè¿ãç¹
T? get valueOrNull {
if (hasValue) return value;
return null;
}
åProvideréã§ã®åãæž¡ãäŸ
N çªç ãã§æçž®ã§ãããmono ãã起祚ã®ã€ã·ã¥ãŒã³ã¡ã³ãã«æžãæãäŸãèšèŒãããŠããããããèŠãã®ãäžçªããããããã§ãïŒæ¯åºŠããããšãããããŸãðïŒã
2.0.0-dev.9ãŸã§ã®æžãæ¹äŸ
// â StreamProvider
final initialProvider = StreamProvider<int>((ref) => Stream.value(1));
// â¡StreamProvider -> FutureProvider
final twiceAfterSecondProvider = FutureProvider<int>((ref) async {
final value = await ref.watch(initialProvider.future); // -> 1
return Future<int>.delayed(const Duration(seconds: 1), () {
// 1 -> 2
return value * 2;
});
});
// â¢FutureProvider -> Provider<AsyncValue>
final twiceProvider = Provider<AsyncValue<int>>((ref) {
final value = ref.watch(twiceAfterSecondProvider);
return value.whenData((value) {
// 2 -> 4
return value * 2;
});
});
// â£Provider<AsyncValue> -> StreamProvider
final moreTwiceProvider = StreamProvider<int>((ref) {
// 4 -> 8
return ref.watch(twiceProvider.stream).map((e) => e * 2);
});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(moreTwiceProvider).value; // -> 8
}
copyWithPreviousã®æåã现ãããã³ããªã³ã°ãã
çŽåã®ç¶æ
ãç¶æããcopyWithPreviousã¡ãœãã ã§ç޹ä»ããéåæ Provider ã§ã¯ããšãã«äœãæå®ããªããã°ããã©ã«ãã§ copyWithPrevious ã®æåãšãªããŸãããv2.1.0 ããããŸããŸãªèŠä»¶ã«å¯Ÿå¿ã§ãããã现ãããã³ããªã³ã°ãã§ããããã«ãªããŸããã
Added AsyncValue.when(skipLoadingOnReload: bool, skipLoadingOnRefresh: bool, skipError: bool) flags to give fine control over whether the UI should show loading or data/error cases.
https://pub.dev/packages/riverpod/changelog#210
å
·äœçã«ã¯ãå€ã®åãåºããšäŸ¿å©ãªã·ã³ã¿ãã¯ã¹ ã§ç޹ä»ãã .when ãªã©ã®ã·ã³ã¿ãã¯ã¹å©çšæã«ã以äžã® bool ããããã£ãæå®ã§ããŸããããã©ã«ãå€ãšæåã¯ããã¥ã¡ã³ãããåç
§ããŸããã
| boolãããã㣠| ããã©ã«ã | æå |
|---|---|---|
skipLoadingOnReload |
false |
ref.watch ã§ã®ãªããŒãæïŒåè©äŸ¡æïŒã« AsyncLoading ç¶æ
ã«ãããåŠã |
skipLoadingOnRefresh |
true |
ref.refresh ã§ã®ãªãã¬ãã·ã¥æã« AsyncLoading ç¶æ
ã«ãããåŠã |
skipError |
false | çŽåã® value ãååšããå Žåã«ããšã©ãŒã§ã¯ãªãdataãåŒã³åºããåŠã |
ããšãã«äœãæå®ããªããã°ããã©ã«ãã§ copyWithPrevious ã®æåãšãªããŸããããšåè¿°ããéããäœãæå®ããªãå Žå㯠copyWithPrevious ãåãããªãã¬ãã·ã¥æäœãªã©ããŠããããããLoading ç¶æ
ïŒ=AsyncLoading ã¯ã©ã¹ïŒãã«ã¯ãªããŸããããïŒããšãªã£ãæ¹ã¯ isRefreshing ãå床ãåç
§ãã ããã
äžæ¹ã§ãref.watch ã«ãããããã€ããåè©äŸ¡ãããå Žåã¯äžåºŠ AsyncLoading ãçµç±ããŸãïŒããã¥ã¡ã³ãã§ã¯ãã®æåããªãã¬ãã·ã¥ãšå¯Ÿæ¯ããŠãªããŒããšè¡šçŸããŠããŸãïŒã
äŸã以äžã«ç€ºããŸããæ°å€ãè¿ã someProvider 㯠doubleEnabledProvider ã watch ããŠããŸãããã®å ŽåãdoubleEnabledProvider ã®å€ãæŽæ°ããããšãsomeProvider ãåè©äŸ¡ãããïŒ=someProvider ã®ã³ãŒã«ããã¯å
ãåŒã°ããïŒãããAsyncLoading ã¯ã©ã¹ãçµç±åŸã«çµæãåŸãããŸãã
// æ°å€ã2åã«ãããåŠãã管çãããããã€ã
// `someProvider`ããwatchãããŠãã
final doubleEnabledProvider = StateProvider((ref) => false);
final someProvider = FutureProvider.autoDispose((ref) async {
await Future<void>.delayed(const Duration(seconds: 2));
// `doubleEnabledProvider`ãæŽæ°ããããšãåç
§å
ã§ãã`someProvider`ãåè©äŸ¡ããã
// ã€ãŸãã`doubleEnabledProvider`ãæŽæ°ããã床ã«AsyncLoadingã¯ã©ã¹ãçµç±ãã
return Future.value(42 * (ref.watch(doubleEnabledProvider) ? 2 : 1));
});
@override
Widget build(BuildContext context, WidgetRef ref) {
// åè©äŸ¡ããããšAsyncLoading, ã€ãŸãCircularProgressIndicatorã®ã¯ã«ã¯ã«è¡šç€ºã«ãªããŸã
// 察ããŠãªãã¬ãã·ã¥æäœã§ã¯è¡šç€ºãããããšåãæ¿ãããŸãã
return ref.watch(someProvider).when(
// skipLoadingOnReload: false,
// skipLoadingOnRefresh: true,
// skipError: false,
loading: CircularProgressIndicator.new,
data: (data) => Text('data: $data'),
error: (error, stackTrace) => Text('error: $error'),
);
}
ããããã®éãããŸãšãããšä»¥äžãšãªããŸãã
## Reloading
- 奿©: `doubleEnabledProvider`ãæŽæ°ãããæïŒåè©äŸ¡ïŒ
- ç¶æ
å€å: AsyncData â AsyncLoding(isReloading:true) â AsyncData
## Refresh
- 奿©: pull-to-refreshæäœãªã©ã§`ref.refresh`ã«ãã£ãŠãªãã¬ãã·ã¥ãããæ
- ç¶æ
å€å: AsyncData â AsyncData(isRefreshing:true) â AsyncData
以äžãããã©ã«ãã®æåã§ããããã现ãããã³ããªã³ã°ã§ããã®ãæ¬ã»ã¯ã·ã§ã³åé ã§ç޹ä»ãã bool ããããã£ãšãªããŸããããšãã°ãããªãã¬ãã·ã¥æäœæã§ã AsyncLoading ã¯ã©ã¹ãçµç±ã㊠copyWithPrevious ã®æ¯ãèããããã«äžããååŸãããããšãã£ãèŠæã skipLoadingOnRefresh ã false ã«ããããšã§ç°¡åã«æºãããŸãã
æ¬ã»ã¯ã·ã§ã³ã®ããããã£ã¯ãããŸã§ãªãã·ã§ãã«ãªã®ã§ãããããããšæããæ¹ã¯ããã©ã«ãã®æåã«åŸããšããéžæãããã§ããã¡ãªã¿ã«ãå人çã«ãåºæ¬ã¯ããã©ã«ãå€ã«åŸãæ¹éã§ãïŒããã©ã«ãããŠãŒã¶ãŒäœéšçã«ãã£ãšãèªç¶ãªæåã«ãªã£ãŠãããšæãã®ãçç±ã§ãïŒã
ãŸãšã
ä»å㯠Riverpod(v2)ã® AsyncValue ã顿ã«ãåã®å€ãåæãããã®æåã isRefreshingã䟿å©ãªã·ã³ã¿ãã¯ã¹ãªã©ãåãæ±ã£ãŠããŸãããå人çã«ãæã¯ StateNotifierProvider ã䜿ã£ãŠ MVVM ã£ãœãäœãã§å®è£
ããŠããéœåã§ AsyncValue ãåãæ±ãæ©äŒãæ£çŽããŸããªãã£ãã®ã§ãããççµåçã«åãé¢ããŠå®è£
ããããã«ãªã£ãŠãã㯠FutureProvider/StreamProvider ãå©çšããæ©äŒãå¢ã AsyncValue ã®äœ¿ããããã«æ°ã¥ãå§ããŸããã
ãªããšãªããéåæåŠçã®ç¶æ
ããã³ããªã³ã°ããŠãããã¯ã©ã¹ãçšåºŠã«æã£ãŠãã AsyncValue ã§ãããå·çã«ããã£ãŠè²ã
調ã¹ãŠã¿ããšãåã®å€ã®åæããšã©ãŒãã³ããªã³ã°ã®æªçœ®ãåçš® extension ãªã©ãéçºè
ã«ãšã£ãŠæ±ããããã±ã¢ããããããªãããŠããããšã«æ°ã¥ããŸããã
ä»åã¯ãäžèšã®ããã¥ã¡ã³ãã«ã¯ãããŸã§æåãèšèŒãããŠã¯ããªããã®ã®ã
åé ã«ãã®ããã«èšèŒããŸããããAPI ããã¥ã¡ã³ãã¯ãããšãã«ãã¹ãã³ãŒããäžå¯§ã«æžãããŠãããããããèªããªãå®éã«æå ã§åãããããããªã©ãåãããŠã¿ããšãã£ãšãçè§£ãé²ã¿ãŸããã
å·çæç¹ã§ã¯ãŸã Prerelease çã§ããããå®å®çæäŸãããŠèœã¡çããŠããŸããããããããæ¬æ Œçã« Riverpod ã䜿ãå§ããæ¹ã v2ãžä»¥éããæ¹ãªã©ã®åèã«ãªãã°å¬ããã§ãã
Discussion