How to cache rxdart streams in flutter for infinite scroll - dart

class PollScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
bloc.paginatePolls(null, null);
return StreamBuilder(
stream: bloc.polls,
builder: (context, AsyncSnapshot<List<PollModel>> snapshot) {
if (snapshot.data == null || snapshot.data.length < 1) {
return Text('loading...');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, int index) {
final PollModel curItem = snapshot.data[index];
return Card(
//Render Logic
);
},
);
});
}
}
class Bloc {
final Repository _repository = Repository();
final PublishSubject<List<PollModel>> _polls = PublishSubject<List<PollModel>>();
Observable<List<PollModel>> get polls => _polls.stream;
paginatePolls(int count, String last) async {
final List<PollModel> polls = await _repository.paginatePolls(count, last);
_polls.sink.add(polls);
}
dispose(){
_polls.close();
}
}
final bloc = Bloc();
I am from react native background in terms of mobile dev and I have used tools like apollo and redux so rxdart is a little bit confusing for me. paginatePolls just retrieves simple list of objects from the server and adds to the stream and PollScreen class is rendering the result. Everything works fine but I am curious how I can cache the first paginatePolls requests so that I can make subsequent queries with count (number of docs to return) last (id of the last item returned from previous result) and simply append the result to what is already there.
In apollo and redux I would just be adding more and more docs to the cache as I make more requests but since rxdart is a stream, I am unsure of which approach is the most effective.
I have thought of cacheing with sqlite but seems like a huge over kill + unsure if it would be fast enough.
Next method I have thought of is just to create a list in the bloc and keep adding items to it as more requests are made. Then stream the whole list every time. But this would mean re-creating the entire list for every stream e.g. first request render 1-50, second render 1-100, third render 1-150 where what would be preferable is - first render 1-50, second render 51-100 and just attach below the first render, etc.
How do you implement counterparts of apollo and redux cache in flutter using rxdart?

I've been stuck on this similar problem. I was thinking of keeping a Map in my repository (class where you make your network requests) and just do a look up in the map from there. But I am having issues with this, since I use the "compute" method, and whatever you pass into the "compute" function cannot be a instance method. (https://flutter.dev/docs/cookbook/networking/background-parsing)
I looked online and a few people advise against a global bloc, I am from a React Native + Redux background as well, but I really want to use the bloc pattern the right way, wish there were better examples out there.

Related

Flutter provider, question around Dart syntax

I'm relatively new to Dart/Flutter,
Just struggling to understand some code/syntax and wondered if someone can help explain.
Im looking at the example of setting up multiple providers and I cant get my head round the code for setting up the update..
providers: [
// In this sample app, CatalogModel never changes, so a simple Provider
// is sufficient.
Provider(create: (context) => CatalogModel()),
// CartModel is implemented as a ChangeNotifier, which calls for the use
// of ChangeNotifierProvider. Moreover, CartModel depends
// on CatalogModel, so a ProxyProvider is needed.
ChangeNotifierProxyProvider<CatalogModel, CartModel>(
create: (context) => CartModel(),
update: (context, catalog, cart) {
cart.catalog = catalog;
return cart;
},
),
],
Specifically...
update: (context, catalog, cart) {
cart.catalog = catalog;
return cart;
}
I thought it was a function that takes in 3 parameters context, catelog, cart
But I dont see anywhere where they are first instantiated
Can anyone explain what is going on here?
Thanks
update: denotes a parameter to the ChangeNotifierProxyProvider<CatalogModel, CartModel> constructor, passing it an anonymous function that takes three parameters. The code in (or near) the ChangeNotifierProxyProvider will be invoking this function as necessary.

What is the purpose of `FutureOr`?

Dart offers a FutureOr class, that allows writing:
FutureOr<int> future;
future = 42; // valid
future = Future.value(42); // also valid
I would assume that FutureOr would be useful to remove the unnecessary delay caused by the event loop if the value can be read synchronously.
But that doesn't seem to be the case, as showcased by:
import 'dart:async';
void main() async {
print('START');
futureOrExample();
print('END');
}
void futureOrExample() async {
FutureOr<int> futureOr = 42;
print('before await');
await futureOr;
print('end await');
}
which prints:
START
before await
END
end await
when I would expect:
START
before await
end await
END
In that case, why does FutureOr (or more generally await 42) work this way?
Similarly, what's the purpose of FutureOr in that situation since it produces the same result as Future?
I know that I could use SynchronousFuture to achieve the desired result, but I'm just trying to understand what's the use of FutureOr.
The use of FutureOr, as introduced with Dart 2, is to allow you to provide either a value or a future at a point where the existing Dart 1 API allowed the same thing for convenience, only in a way that can be statically typed.
The canonical example is Future.then. The signature on Future<T> is Future<R> then<R>(FutureOr<R> action(T value), {Function onError}).
The idea is that you can have an action on the future's value which is either synchronous or asynchronous. Originally there was a then function which took a synchronous callback and a chain function which took an asynchronous callback, but that was highly annoying to work with, and in good Dart 1 style, the API was reduced to one then method which took a function returning dynamic, and then it checked whether it was a future or not.
In Dart 1 it was easy to allow you to return either a value or a future. Dart 2 was not as lenient, so the FutureOr type was introduced to allow the existing API to keep working. If we had written the API from scratch, we'd probably have done something else, but migrating the existing asynchronous code base to something completely different was not an option, so the FutureOr type was introduced as a type-level hack.
The await operation was also originally defined to work on any object, long before FutureOr existed. For consistency and smaller code, an await e where e evaluated to a non-future would wrap that value in a future and await that. It means that there is only one quick and reusable check on a value (is it a future, if not wrap it), and then the remaining code is the same. There is only one code-path.
If the await worked synchronously on non-Future values, there would have to be a synchronous code path running through the await, as well as an asynchronous path waiting for a future. That would potentially double the code size, for example when compiling to JavaScript (or worse, if there were more awaits in the same control flow, you could get exponential blow-up for a naive implementation). Even if you avoided that by just calling the continuation function synchronously, it would likely be confusing to some readers that an await would not introduce an asynchronous gap. A mistake around that can cause race conditions or things happening in the wrong order.
So, the original design, predating FutureOr, was to make all await operations actually wait.
The introduction of FutureOr did not change this reasoning, and even if it did, it would now be a breaking change to not wait in places where people expect their code to actually give time for other microtasks to run.
The await keyword always lock the function execution.
Writing:
await 42
Is equivalent to:
await Future.value(42)
The reason being:
This is how await works in Javascript
it makes the behavior of await consistent.
Now, what's the purpose of FutureOr then?
FutureOr was never intended as a way to potentially make await synchronous.
Instead, it is an implementation detail of Future.
Without FutureOr, writing the following would not compile:
Future(() {
return 42; // compile error, not a Future
});
Future<int> future;
future.then((value) {
return value * 2; // compile error, not a Future
});
Instead, we would have to wrap all values in a Future.value like so:
Future<int> future;
future.then((value) {
return Future.value(value * 2);
});
for those who are still confused
I found good explanation https://itnext.io/what-is-futureor-in-dart-flutter-681091162c57 without diving into details
This piece of code could explain the target and real use cases of FutureOr
abstract class IDBService {
FutureOr<String> fetch();
}
class FirebaseRemoteService extends IDBService {
#override
Future<String> fetch() async => await 'data';
}
class LocalHiveDbService extends IDBService {
#override
String fetch() => 'data';
}
so in implementations of IDBService
the return type can be Future or String at the same time now!
Coming late to the discussion.
Updating my Dart comprehension - pardon my C++/JS -ish approach.
Seems like this would be useful for singleton initiation. Consider following:
import 'dart:async';
class AClass {
static String _info = '';
static FutureOr<String> get info async {
if (_info.isEmpty) {
print('--> is empty...');
_info = await Future.delayed(Duration(seconds:2),
() => "I'm alive!!");
}
else {
print('--> not empty');
}
return _info;
}
}
Future<void> main() async {
String info = await AClass.info;
print('Fist call: ' + info);
info = await AClass.info;
print('Second call: ' + info);
}
It works as expected - in either case, whether the _info member has been instantiated or not, the getter returns a valid string.
It works fine if I just use a Future<String> specifier in the getter, too. The current implementation makes FutureOr seem mostly like a self-documentation exercise (can return a Future<String> or a String...)
But, even if await currently always locks the execution, a future update may allow it to work as expected, in which case using the FutureOr construct would anticipate updates.
(Aside: I imagine this example could be condensed using an Optional wrapping the _info member, but that's a different exercise...)
I needed to use FutureOr today. I wanted to call a function that might be asynchronously (not always).
String callbackOne() => "hello";
Future<String> callbackTwo() async => (await Future.delayed(Duration(seconds: 1),() => "This is a sentence"));
Problem
I can do getLengthOfResult(callbackOne), but not getLengthOfResult(callbackTwo). Conversely, if accept an async callback, I can't use the sync callback.
DartPad
Future<int> getLengthOfResult(String Function() callback) async {
return callback().length;
}
Solution
DartPad
Future<int> getLengthOfResult(FutureOr<String> Function() callback) async {
// I can await on callbackOne, even though it returns a String.
final result = await callback();
return result.length;
}
main() {
getLengthOfResult(callbackOne);
getLengthOfResult(callbackTwo);
}

How to combine multi data steams in one BLOC?

I have a widget to represent list of stores sorted by nearest to the user current locations also filtering should be applied.
Data in:
Stores data coming from stream of Firestore collection
Current user location from geolacator.
Filtering options from shared preferences
(can be changed any time)
List sorting mode selected by user
Data out: Filtered, sorted, list of stores.
What pattern is best practice in this case?
rxdart : https://pub.dartlang.org/packages/rxdart
if you wanna combine data together you can use
var myObservable = Observable.combineLatest3(
myFirstStream,
mySecondStream,
myThirdStream,
(firstData, secondData, thirdData) => print("$firstData $secondData $thirdData"));
you can combine from ( combineLatest2, combineLatest... combineLatest9 )
or
CombineLatestStream
like this example
CombineLatestStream.list<String>([
Stream.fromIterable(["a"]),
Stream.fromIterable(["b"]),
Stream.fromIterable(["C", "D"])])
.listen(print);
Numbers 2, 3 and 4 are inputs to the bloc that you'd send in through sinks. The bloc listens on those sinks and updates the Firestore query accordingly. This alone might be enough to make Firestore send the appropriate snapshots to the output stream the widget is listening to.
If you can't sort or filter how you want directly with Firestore's APIs, you can use stream.map or apply a StreamTransformer on it. The transformer gives you a lot of flexibility to listen to a stream and change or ignore events on the fly by implementing its bind method.
So you can do something like:
Stream<Store> get stores => _firestoreStream
.transform(filter)
.transform(sort);
Have a look at this page for streams in dart in general, and look into rxdart for more complex stream manipulations.
From personal experience I found having multiple inputs to a block leads to hard to test code. The implicit concurrency concerns inside the block lead to confusing scenarios.
The way I built it out in my Adding testing to a Flutter app post was to create a single input stream, but add markers to the messages notating which data stream the message was a part of. It made testing sane.
In this situation, I think there are multiple asynchronous processing. This implementation can be complicated. And there is a possibility of race condition.
I will implement as follows.
Separate streams of Model from Firestore and user-visible ViewModel in Bloc. Widgets listen to only ViewModel.(eg. with StreamBuilder)
Limit Business logic processing only in Bloc. First, relocate processing with SharedPreferences into Bloc.
Create UserControl class just for user input.
Branch processing depends on user input type of extended UserControl
I hope you this will help you.
For example:
import 'dart:async';
import 'package:rxdart/rxdart.dart';
class ViewModel {}
class DataFromFirestoreModel {}
abstract class UserControl {}
class UserRequest extends UserControl {}
class UserFilter extends UserControl {
final String keyWord;
UserFilter(this.keyWord);
}
enum SortType { ascending, descending }
class UserSort extends UserControl {
final SortType sortType;
UserSort(this.sortType);
}
class Bloc {
final controller = StreamController<UserControl>();
final viewModel = BehaviorSubject<ViewModel>();
final collection = StreamController<DataFromFirestoreModel>();
Bloc() {
controller.stream.listen(_handleControl);
}
_handleControl(UserControl control) {
if (control is UserRequest) {
_handleRequest();
} else if (control is UserFilter) {
handleFilter(control.keyWord);
} else if (control is UserSort) {
handleSort(control.sortType);
}
}
_handleRequest() {
//get location
//get data from sharedPreferences
//get data from firestore
ViewModel modifiedViewModel; // input modifiedViewModel
viewModel.add(modifiedViewModel);
}
handleSort(SortType sortType) {
final oldViewModel = viewModel.value;
//sorting oldViewModel
ViewModel newViewModel; // input sorted oldViewModel
viewModel.add(newViewModel);
}
handleFilter(String keyWord) {
//store data to sharedPreferences
//get data from Firestore
ViewModel modifiedViewModel; // input modifiedViewModel
viewModel.add(modifiedViewModel);
}
}

Widget re-render when I focus on a TextField - Bloc Pattern

I'm using a BLoC to keep state between two nested FullScreenDialogs.
I'm initializing the bloc when I push the first screen, like so
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => ProductBlocProvider(child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
ProductEntryScreen has a bunch of TextFields and a button than opens a new FullScreenDialog. This new Screen also has TextFields.
The problem I'm having is that every time I write on a TextField on the second FullScreenDialog, the onPressed function where I start the ProductBlocProvider runs again.
And that re-run is causing the Bloc to create a new instance, so I end up loosing the state.
What I want to do?
Maybe I'm doing it wrong so I'll explain what I'm trying to achieve.
I want to keep state between the two FullScreenDialogs while I fill all the fields, and when I'm done I want to press a button that send all of the data (both screens) to a database.
The problem is that I was creating the instance of the bloc inside the provider in the builder function of the MaterialPageRoute.
That builder function was being called repeatedly, and creating a new instance of the bloc every time. The solution was to take out from the builde function the creation of the bloc instance, like this:
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//Here I create the instance
var _bloc = ProductBloc();
Navigator.of(context).push(MaterialPageRoute(
//And I pass the bloc instance to the provider
builder: (BuildContext context) => ProductBlocProvider(bloc: _bloc, child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
The package get_it may be of help to you. get_it is a service locator library, and uses a Map to store the registered objects; therefore, it provides access at a complexity of O(1), which means it's incredibly fast. The package comes with a singleton GetIt which you can use like so,
// Create a global variable (traditionally called sl or locator)
final sl = GetIt.instance; // There is also a shorthand GetIt.i
// ...
// Then, maybe in a global function called initDi(),
// you could register your dependencies.
sl.registerLazySingleton(() => ProductBloc());
registerLazySingleton() or registerSingleton() will always
return the same instance; lazily (i.e., when first called)
or at app start-up respectively.
If you want to create a new instance every time, use registerFactory() instead (I put this here even though it's not exactly what you want).
For example,
sl.registerFactory(() => ValidatorCubit());
And it could be accessed like this,
MultiBlocProvider(
providers: [
// The type is inferred here
BlocProvider<AuthenticationBloc>(create: (_) => sl()),
// The type is explicitly given here
BlocProvider(create: (_) => sl<ProductsBloc>()),
],
child: ProductsScreen(),
),
This example primarily shows you how it can be done with the flutter_bloc library, but get_it works anywhere, even in non-flutter dart projects.
If you need more functionality, do make sure to read the docs for this package. It is well documented, and contains (almost) every feature you might need, including scoping.
Also, this approach allows you to use the interface pattern, making the code much more maintainable and testable, as you will have to change just one place to use a different implementation.

IndexedWidgetBuilder, how does it know the index?

This is mostly a conceptual question as I'm new to Dart and I guess I'm not understanding the semantics of the language here.
In IndexedWidgetBuilder,
Widget IndexedWidgetBuilder (
BuildContext context,
int index
)
who exactly is giving a value to index?
When this "thing" is used, for example:
itemBuilder: (context, i) {
blablabla
},
"context" and "i" are never initialized and they magically have a value. Who is defining this value and where?
IndexedWidgetBuilder is a typedef that defines a function that takes a BuildContext and an int, and returns a Widget.
/// Signature for a function that creates a widget for a given index, e.g., in a
/// list.
///
/// Used by [ListView.builder] and other APIs that use lazily-generated widgets.
typedef Widget IndexedWidgetBuilder(BuildContext context, int index);
So, when defining itemBuilder you are providing the Widget with a function that it can call, when it wants to build an item. When the Widget is building itself it will call this function many times to build each of its children. Simplistically, it may call this function with i=0, then 1, then 2, etc.
If your Widget only has 3 children, it would be easier to just pass then as a List, but if your Widget has a thousand children this would be inefficient - and this is where the builder comes in. The Widget will try to only call the itemBuilder function for the child widgets that it actually needs, and not for any that are, say, off the top or bottom of the screen.
So, to answer your question, the Widget passes the context and i to your itemBuilder function when it calls it (typically multiple times) to build some or all of its children. i represents the ith child, so that your builder function knows which child it is being asked to build.
Edit
The dartdoc of IndexedWidgetBuilder says that it is used by ListView.builder.
The dartdoc of ListView.builder says
Providing a non-null itemCount improves the ability of the [ListView]
to estimate the maximum scroll extent. The itemBuilder callback will
be called only with indices greater than or equal to zero and less
than itemCount. The itemBuilder should actually create the widget
instances when called.
The ListView.builder named constructor constructs a SliverChildBuilderDelegate, passing in the itemBuilder as builder.
It gets used in the SliverChildBuilderDelegate's build method, here:
#override
Widget build(BuildContext context, int index) {
assert(builder != null);
if (index < 0 || (childCount != null && index >= childCount))
return null;
Widget child = builder(context, index); // <- your callback is called
if (child == null)
return null;
if (addRepaintBoundaries)
child = new RepaintBoundary.wrap(child, index);
if (addAutomaticKeepAlives)
child = new AutomaticKeepAlive(child: child);
return child;
}
So, index comes from the SliverChildBuilderDelegate's build method. You could keep walking backwards to see who calls that.

Resources