gpt4 book ai didi

flutter - 使用 BLoC 在 init 上加载数据

转载 作者:行者123 更新时间:2023-12-03 02:43:24 24 4
gpt4 key购买 nike

Flutter 的新成员 BLoC。基于搜索模板构建,希望在应用加载时加载数据 ( items ),而不是在状态更改时加载。

方法getCrystals()搜索意图时返回正确数据.isEmpty但是如何在应用加载时完成呢?

Crystal_repo.dart

abstract class CrystalRepo {
Future<BuiltList<Crystal>> getCrystals();

Future<BuiltList<Crystal>> searchCrystal({
@required String query,
int startIndex: 0,
});
}

Crystal_repo_impl.dart
class CrystalRepoImpl implements CrystalRepo {
static const _timeoutInMilliseconds = 120000; // 2 minutes
final Map<String, Tuple2<int, CrystalResponse>> _cached = {};

///
final CrystalApi _api;
final Mappers _mappers;

CrystalRepoImpl(this._api, this._mappers);

@override
Future<BuiltList<Crystal>> searchCrystal({
String query,
int startIndex = 0,
}) async {
assert(query != null);
final crystalsResponse = await _api.searchCrystal(
query: query,
startIndex: startIndex,
);

final crystal = crystalsResponse.map(_mappers.crystalResponseToDomain);
return BuiltList<Crystal>.of(crystal);
}

@override
Future<BuiltList<Crystal>> getCrystals() async {
final crystalsResponse = await _api.getCrystals();
final crystal = crystalsResponse.map(_mappers.crystalResponseToDomain);
return BuiltList<Crystal>.of(crystal);
}
}

search_bloc.dart
class SearchBloc implements BaseBloc {
/// Input [Function]s
final void Function(String) changeQuery;
final void Function() loadNextPage;
final void Function() retryNextPage;
final void Function() retryFirstPage;
final void Function(String) toggleFavorited;

/// Ouput [Stream]s
final ValueStream<SearchPageState> state$;
final ValueStream<int> favoriteCount$;

/// Subscribe to this stream to show message like snackbar, toast, ...
final Stream<SearchPageMessage> message$;

/// Clean up resource
final void Function() _dispose;

SearchBloc._(
this.changeQuery,
this.loadNextPage,
this.state$,
this._dispose,
this.retryNextPage,
this.retryFirstPage,
this.toggleFavorited,
this.message$,
this.favoriteCount$,
);

@override
void dispose() => _dispose();

factory SearchBloc(final CrystalRepo crystalRepo, final FavoritedCrystalsRepo favCrystalsRepo,){
assert(crystalRepo != null);
assert(favCrystalsRepo != null);

/// Stream controllers, receive input intents
final queryController = PublishSubject<String>();
final loadNextPageController = PublishSubject<void>();
final retryNextPageController = PublishSubject<void>();
final retryFirstPageController = PublishSubject<void>();
final toggleFavoritedController = PublishSubject<String>();
final controllers = [
queryController,
loadNextPageController,
retryNextPageController,
retryFirstPageController,
toggleFavoritedController,
];

/// Debounce query stream
final searchString$ = queryController
.debounceTime(const Duration(milliseconds: 300))
.distinct()
.map((s) => s.trim());

/// Search intent
final searchIntent$ = searchString$.mergeWith([
retryFirstPageController.withLatestFrom(
searchString$,
(_, String query) => query,
)
]).map((s) => SearchIntent.searchIntent(search: s));

/// Forward declare to [loadNextPageIntent] can access latest state via [DistinctValueConnectableStream.value] getter
DistinctValueConnectableStream<SearchPageState> state$;

/// Load next page intent
final loadAndRetryNextPageIntent$ = Rx.merge(
[
loadNextPageController.map((_) => state$.value).where((currentState) {
/// Can load next page?
return currentState.crystals.isNotEmpty &&
currentState.loadFirstPageError == null &&
currentState.loadNextPageError == null;
}),
retryNextPageController.map((_) => state$.value).where((currentState) {
/// Can retry?
return currentState.loadFirstPageError != null ||
currentState.loadNextPageError != null;
})
],
).withLatestFrom(searchString$, (currentState, String query) =>
Tuple2(currentState.crystals.length, query),
).map(
(tuple2) => SearchIntent.loadNextPageIntent(
search: tuple2.item2,
startIndex: tuple2.item1,
),
);

/// State stream
state$ = Rx.combineLatest2(
Rx.merge([searchIntent$, loadAndRetryNextPageIntent$]) // All intent
.doOnData((intent) => print('[INTENT] $intent'))
.switchMap((intent) => _processIntent$(intent, crystalRepo))
.doOnData((change) => print('[CHANGE] $change'))
.scan((state, action, _) => action.reduce(state),
SearchPageState.initial(),
),
favCrystalsRepo.favoritedIds$,
(SearchPageState state, BuiltSet<String> ids) => state.rebuild(
(b) => b.crystals.map(
(crystal) => crystal.rebuild((b) => b.isFavorited = ids.contains(b.id)),
),
),

).publishValueSeededDistinct(seedValue: SearchPageState.initial());

final message$ = _getMessage$(toggleFavoritedController, favCrystalsRepo, state$);

final favoriteCount = favCrystalsRepo.favoritedIds$
.map((ids) => ids.length)
.publishValueSeededDistinct(seedValue: 0);

return SearchBloc._(
queryController.add,
() => loadNextPageController.add(null),
state$,
DisposeBag([
...controllers,
message$.listen((message) => print('[MESSAGE] $message')),
favoriteCount.listen((count) => print('[FAV_COUNT] $count')),
state$.listen((state) => print('[STATE] $state')),
state$.connect(),
message$.connect(),
favoriteCount.connect(),
]).dispose,
() => retryNextPageController.add(null),
() => retryFirstPageController.add(null),
toggleFavoritedController.add,
message$,
favoriteCount,
);
}
}

/// Process [intent], convert [intent] to [Stream] of [PartialStateChange]s
Stream<PartialStateChange> _processIntent$(
SearchIntent intent,
CrystalRepo crystalRepo,
) {
perform<RESULT, PARTIAL_CHANGE>(
Stream<RESULT> streamFactory(),
PARTIAL_CHANGE map(RESULT a),
PARTIAL_CHANGE loading,
PARTIAL_CHANGE onError(dynamic e),
) {
return Rx.defer(streamFactory)
.map(map)
.startWith(loading)
.doOnError((e, s) => print(s))
.onErrorReturnWith(onError);
}

searchIntentToPartialChange$(SearchInternalIntent intent) =>
perform<BuiltList<Crystal>, PartialStateChange>(
() {
if (intent.search.isEmpty) {
return Stream.fromFuture(crystalRepo.getCrystals());
}
return Stream.fromFuture(crystalRepo.searchCrystal(query: intent.search));
},
(list) {
final crystalItems = list.map((crystal) => CrystalItem.fromDomain(crystal)).toList();
return PartialStateChange.firstPageLoaded(crystals: crystalItems, textQuery: intent.search,);
},
PartialStateChange.firstPageLoading(),
(e) {
return PartialStateChange.firstPageError(error: e,textQuery: intent.search,);
},
);

loadNextPageIntentToPartialChange$(LoadNextPageIntent intent) =>
perform<BuiltList<Crystal>, PartialStateChange>();

return intent.join(
searchIntentToPartialChange$,
loadNextPageIntentToPartialChange$,
);
}

search_state.dart
abstract class SearchPageState implements Built<SearchPageState, SearchPageStateBuilder> {
String get resultText;

BuiltList<CrystalItem> get crystals;

bool get isFirstPageLoading;

@nullable
Object get loadFirstPageError;

bool get isNextPageLoading;

@nullable
Object get loadNextPageError;

SearchPageState._();

factory SearchPageState([updates(SearchPageStateBuilder b)]) = _$SearchPageState;

factory SearchPageState.initial() {
return SearchPageState((b) => b
..resultText = ''
..crystals = ListBuilder<CrystalItem>()
..isFirstPageLoading = false
..loadFirstPageError = null
..isNextPageLoading = false
..loadNextPageError = null);
}
}

class PartialStateChange extends Union6Impl<
LoadingFirstPage,
LoadFirstPageError,
FirstPageLoaded,
LoadingNextPage,
NextPageLoaded,
LoadNextPageError> {
static const Sextet<LoadingFirstPage, LoadFirstPageError, FirstPageLoaded,
LoadingNextPage, NextPageLoaded, LoadNextPageError> _factory =
Sextet<LoadingFirstPage, LoadFirstPageError, FirstPageLoaded,
LoadingNextPage, NextPageLoaded, LoadNextPageError>();

PartialStateChange._(
Union6<LoadingFirstPage, LoadFirstPageError, FirstPageLoaded,
LoadingNextPage, NextPageLoaded, LoadNextPageError>
union)
: super(union);

factory PartialStateChange.firstPageLoading() {
return PartialStateChange._(
_factory.first(
const LoadingFirstPage()
)
);
}

factory PartialStateChange.firstPageError({
@required Object error,
@required String textQuery,
}) {
return PartialStateChange._(
_factory.second(
LoadFirstPageError(
error: error,
textQuery: textQuery,
),
),
);
}

factory PartialStateChange.firstPageLoaded({
@required List<CrystalItem> crystals,
@required String textQuery,
}) {
return PartialStateChange._(
_factory.third(
FirstPageLoaded(
crystals: crystals,
textQuery: textQuery,
),
)
);
}

factory PartialStateChange.nextPageLoading() {
return PartialStateChange._(
_factory.fourth(
const LoadingNextPage()
)
);
}

factory PartialStateChange.nextPageLoaded({
@required List<CrystalItem> crystals,
@required String textQuery,
}) {
return PartialStateChange._(
_factory.fifth(
NextPageLoaded(
textQuery: textQuery,
crystals: crystals,
),
),
);
}

factory PartialStateChange.nextPageError({
@required Object error,
@required String textQuery,
}) {
return PartialStateChange._(
_factory.sixth(
LoadNextPageError(
textQuery: textQuery,
error: error,
),
),
);
}

/// Pure function, produce new state from previous state [state] and partial state change [partialChange]
SearchPageState reduce(SearchPageState state) {
return join<SearchPageState>(
(LoadingFirstPage change) {
return state.rebuild((b) => b..isFirstPageLoading = true);
},
(LoadFirstPageError change) {
return state.rebuild((b) => b
..resultText = "Search for '${change.textQuery}', error occurred"
..isFirstPageLoading = false
..loadFirstPageError = change.error
..isNextPageLoading = false
..loadNextPageError = null
..crystals = ListBuilder<CrystalItem>());
},
(FirstPageLoaded change) {
return state.rebuild((b) => b
//..resultText = "Search for ${change.textQuery}, have ${change.crystals.length} crystals"
..resultText = ""
..crystals = ListBuilder<CrystalItem>(change.crystals)
..isFirstPageLoading = false
..isNextPageLoading = false
..loadFirstPageError = null
..loadNextPageError = null);
},
(LoadingNextPage change) {
return state.rebuild((b) => b..isNextPageLoading = true);
},
(NextPageLoaded change) {
return state.rebuild((b) {
var newListBuilder = b.crystals..addAll(change.crystals);
return b
..crystals = newListBuilder
..resultText =
"Search for '${change.textQuery}', have ${newListBuilder.length} crystals"
..isNextPageLoading = false
..loadNextPageError = null;
});
},
(LoadNextPageError change) {
return state.rebuild((b) => b
..resultText =
"Search for '${change.textQuery}', have ${state.crystals.length} crystals"
..isNextPageLoading = false
..loadNextPageError = change.error);
},
);
}

@override
String toString() => join<String>(_toString, _toString, _toString, _toString, _toString, _toString);
}


search_page.dart
class SearchListViewWidget extends StatelessWidget {
final SearchPageState state;

const SearchListViewWidget({Key key, @required this.state})
: assert(state != null),
super(key: key);

@override
Widget build(BuildContext context) {
final bloc = BlocProvider.of<SearchBloc>(context);

if (state.loadFirstPageError != null) {}


// LOOKING TO HAVE items LOADED ON APP LOAD //

final BuiltList<CrystalItem> items = state.crystals;

if (items.isEmpty) {
debugPrint('items.isEmpty');
}

return ListView.builder(
itemCount: items.length + 1,
padding: const EdgeInsets.all(0),
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
debugPrint('itemBuilder');
if (index < items.length) {
final item = items[index];
return SearchCrystalItemWidget(
crystal: item,
key: Key(item.id),
);
}

if (state.loadNextPageError != null) {
final Object error = state.loadNextPageError;

return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
error is HttpException
? error.message
: 'An error occurred $error',
textAlign: TextAlign.center,
maxLines: 2,
style:
Theme.of(context).textTheme.body1.copyWith(fontSize: 15),
),
SizedBox(height: 8),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: bloc.retryNextPage,
padding: const EdgeInsets.all(16.0),
child: Text(
'Retry',
style: Theme.of(context).textTheme.body1.copyWith(fontSize: 16),
),
elevation: 4.0,
),
],
),
);
}

return Container();
},
);
}
}

最佳答案

最终通过在 app.dart 中传入一个空查询来解决这个问题

home: Consumer2<FavoritedCrystalsRepo, CrystalRepo>(
builder: (BuildContext context, FavoritedCrystalsRepo sharedPref, CrystalRepo crystalRepo) {
final searchBloc = SearchBloc(crystalRepo, sharedPref);
// Do the first search to get first result on init
searchBloc.changeQuery('');
return BlocProvider<SearchBloc>(
child: SearchPage(),
initBloc: () => searchBloc,
);
},

关于flutter - 使用 BLoC 在 init 上加载数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59857104/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com