Skip to content

🍂 A Flutter plugin for loading content asynchronously with Dart stream and RxDart. RxDart loader bloc. Reactive loader bloc. Simple reactive state management container - https://pub.dev/packages/stream_loader

License

Notifications You must be signed in to change notification settings

hoc081098/stream_loader

Repository files navigation

stream_loader alt text

  • A Flutter plugin for loading content asynchronously with Dart Stream and RxDart.
  • RxDart loader bloc.
  • Reactive loader bloc.
  • Simple reactive state management container.

Tests Build example Pub Build Status codecov License: MIT

Getting Started

In your flutter project, add the dependency to your pubspec.yaml

dependencies:
  ...
  stream_loader: <latest_version>

Examples

Usage

1. Model and api

abstract class Comment implements Built<Comment, CommentBuilder> { ... }

class Api {
  Stream<BuiltList<Comment>> getComments() { ... }
  Stream<Comment> getCommentBy({@required int id}) { ... }
}
final api = Api();

2. Create LoaderWidget load comments from api

import 'package:stream_loader/stream_loader.dart';

LoaderWidget<BuiltList<Comment>>(
  blocProvider: () => LoaderBloc(
    loaderFunction: api.getComments,
    refresherFunction: api.getComments,
    initialContent: <Comment>[].build(),
    logger: print,
  ),
  messageHandler: (context, message, bloc) {
    message.fold(
      onFetchFailure: (error, stackTrace) => context.snackBar('Fetch error'),
      onFetchSuccess: (_) {},
      onRefreshSuccess: (data) => context.snackBar('Refresh success'),
      onRefreshFailure: (error, stackTrace) => context.snackBar('Refresh error'),
    );
  },
  builder: (context, state, bloc) {
    if (state.error != null) {
      return ErrorWidget(error: state.error);
    }
    if (state.isLoading) {
      return LoadingWidget();
    }
    return RefreshIndicator(
      onRefresh: bloc.refresh,
      child: CommentsListWidget(comments: state.content),
    );
  }
);

3. Create LoaderWidget load comment detail from api

import 'package:stream_loader/stream_loader.dart';

final Comment comment;
final loadDetail = () => api.getCommentBy(id: comment.id);

LoaderWidget<Comment>(
  blocProvider: () => LoaderBloc(
    loaderFunction: loadDetail,
    refresherFunction: loadDetail,
    initialContent: comment,
    logger: print,
  ),
  messageHandler: (context, message, bloc) {
    message.fold(
      onFetchFailure: (_, __) {},
      onFetchSuccess: (_) {},
      onRefreshFailure: (_, __) {},
      onRefreshSuccess: (_) => context.snackBar('Refresh success'),
    );
  },
  builder: (context, state, bloc) {
    return RefreshIndicator(
      onRefresh: bloc.refresh,
      child: CommentDetailWidget(comment: state.content),
    );
  },
);

Note: Can use LoaderBloc without LoaderWidget easily

class _CommentsState extends State<Comments> {
  LoaderBloc<BuiltList<Comment>> bloc;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    bloc ??= LoaderBloc(
      loaderFunction: api.getComments,
      refresherFunction: api.getComments,
      initialContent: <Comment>[].build(),
      logger: print,
    )..fetch();
  }

  @override
  void dispose() {
    bloc.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<LoaderState<BuiltList<Comment>>>(
      stream: bloc.state$,
      initialData: bloc.state$.value, // <- required because bloc.state$ does not replay the latest value
      builder: (context, snapshot) {
        final state = snapshot.data;
        
        if (state.error != null) {
          return ErrorWidget(error: state.error);
        }
        if (state.isLoading) {
          return LoadingWidget();
        }
        return RefreshIndicator(
          onRefresh: bloc.refresh,
          child: CommentsListWidget(comments: state.content),
        );
      }
    );
  }
}

Change flatten behavior of loaderFunction and refresherFunction.

  • Default behavior of loaderFunction is FlattenStrategy.latest (uses switchMap).
  • Default behavior of refreshFlatMapPolicy is FlattenStrategy.first, (uses exhaustMap).
  • To change them, passing your value to LoaderBloc constructor
LoaderBloc(
  ...,
  loaderFlattenStrategy: FlattenStrategy.concat, // asyncExpand
  refreshFlattenStrategy: FlattenStrategy.latest, // switchMap
);

License

MIT License

Copyright (c) 2020-2022 Petrus Nguyễn Thái Học