stream_loader 
- A Flutterplugin for loading content asynchronously withDart StreamandRxDart.
- RxDartloader bloc.
- Reactive loader bloc.
- Simple reactive state management container.
Author: Petrus Nguyễn Thái Học
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 loaderFunctionisFlattenStrategy.latest(usesswitchMap).
- Default behavior of refreshFlatMapPolicyisFlattenStrategy.first, (usesexhaustMap).
- To change them, passing your value to LoaderBlocconstructor
LoaderBloc(
  ...,
  loaderFlattenStrategy: FlattenStrategy.concat, // asyncExpand
  refreshFlattenStrategy: FlattenStrategy.latest, // switchMap
);
License
MIT License
Copyright (c) 2020-2022 Petrus Nguyễn Thái Học