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.
Related
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.
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.
Is there a way to listen to route changes of the Navigator in Flutter? Basically, I'd like to be notified when a route is pushed or popped, and have the current route on display on screen be returned from the notification
Building on navigator observers, you can also use RouteObserver and RouteAware.
Navigator has observers. You can implement NavigatorObserver and receive notifications with details.
I was also struggling with that and for my purpose the RouteObservers felt overkill and where too much for my needs. The way I handled it lately was to use the onGenerateRoute property inside my MaterialApp. So this solution is more applicable if you are using onGenerateRoute with your app (might especially be useful if you are navigating with arguments). You can read more about it here.
My MaterialApp looks like the following:
runApp(MaterialApp(
title: 'bla',
home: BuilderPage(LoginArguments(false)),
onGenerateRoute: generateRoute
));
The generateRoute method looks like the following:
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'main':
print('Navigated to main')
return MaterialPageRoute(builder: (_) => MainScreen());
case 'register':
print('Navigated to register')
return MaterialPageRoute(builder: (_) => RegisterScreen());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}')),
));
}
}
So everytime I now do for example:
Navigator.pushNamed(
context,
'register',
);
It will print Navigated to register. I think this way is especially helpful if you don't necessarily need to know whether you pushed or popped. With some additional implementation it would also be possible to observe that via injected arguments.
In my controller, via service, I get from DB a list of the names of widgets (eg. chart, calendar, etc). Every widget implements WidgetInterface and may need other services as its own dependencies. The list of widgets can be different for each user, so I don't know which widgets / dependencies I will need in my controller. Generally, I put dependencies via DI, using factories, but in this case I don't know dependencies at the time of controller initialization.
I want to avoid using service locator directly in controller. How can I manage that issue? Should I get a list of the names of widgets in controller factory? And depending on widgets list get all dependencies and put them to controller?
Thanks, Tom
Solution
I solved my issue in a way that suggested Kwido and Sven Buis, it means, I built my own Plugin Manager.
Advantages: I do not need use service locator directly in controller and I have clear and extensible way to get different kinds of widgets.
Thank you.
Create your own Manager, like some sort of ServiceManager, for your widgets.
class WidgetManager extends AbstractPluginManager
Take a look at: Samsonik tutorial - pluginManager. So this way you can inject the WidgetManager and only retrieve the widgets from this manager as your function: validatePlugin, checks whether or not the fetched instance is using the WidgetInterface. Keep in mind that you can still call the parent ServiceManager.
Or keep it simple and build a plugin for your controller that maps your widget names to the service. This plugin can then use the serviceLocator/Manager to retrieve your widget(s), whether they're created by factories or invokableFactories. So you dont inject all the widget directly but only fetch them when they're requested. Something realy simplistic:
protected $map = [
// Widget name within the plugin => Name or class to call from the serviceManager
'Charts' => Widget\Charts::class,
];
public function load($name)
{
if (array_key_exists($name, $this->map)) {
return $this->getServiceManager()->get($this->map[$name]);
}
return null;
}
Injecting all the Widgets might be bad for your performance so you might consider something else, as when the list of your widgets grow so will the time to handle your request.
Hope this helped you and pushed you in some direction.
This indeed is a interesting question. You could consider using Plugins for the widgets, which can be loaded on the fly.
Depency injection is a good practise, but sometimes, with dynamic content, impossible to implement.
Another way to do this, is to make your own widget-manager. This manager then can load the specific widgets you need. The widget-manager can be injected into the controller.
Edit:
As you can see above, same idea from #kwido.
I would use a separate service and inject that into the controller.
interface UserWidgetServiceInterface
{
public function __construct(array $widgets);
public function getWidget($name);
}
The controller factory
class MyControllerFactory
{
public function __invoke(ControllerManager $controllerManager, $name, $requestedName)
{
$serviceLocator = $controllerManager->getServiceLocator();
$userWidgetService = $serviceLocator->get('UserWidgetService');
return new MyController($userWidgetService);
}
}
Then the logic to load the widgets would be moved to the UserWidgetServiceFactory.
public function UserWidgetServiceFactory
{
public function __invoke(ServiceManager $serviceLocator, $name, $requestedName)
{
$userId = 123; // Load from somewhere e.g session, auth service.
$widgetNames = $this->getWidgetNames($serviceLocator, $userId);
$widgets = $this->loadWidgets($serviceManager, $widgetNames);
return new UserWidgetService($widgets);
}
public function getWidgetNames(ServiceManager $sm, $userId)
{
return ['foo','bar'];
}
public function loadWidgets(serviceManager $sm, array $widgets)
{
$w = [];
foreach($widgets as $widgetName) {
$w[$widgetName] = $sm->get($widgetName);
}
return $w;
}
}
The call to loadWidgets() would eager load all the widgets; should you wish to optimise this you could register your widgets as LazyServices
Good morning,
I've been trying to use the removeByName method and it doesn't work.
I'm basically trying to hide a field in my DataObject within the forms that's generated by ModelAdmin, which manages the object.
See sample code below:
///DataObject snippet...
class MyObject extends DataObject{
public static $db = array(
'Title' => 'Varchar',
'Desc' => 'Text',
'Template' => 'HTMLText',
);
//#Override
public function getCMSField(){
$fields = parent::getCMSField();
$fields->removeByName('Template'); /// DOESN'T WORK!!!
return $fields;
}
}//class
Note: I'm not getting any errors. I'm just still seeing the field on the forms (Add and Edit) as usual.
Any help appreciated, thanks.
Okay, I found the issue.
I was just going over the API again for the millionth time, and recognized that I've named the function wrong. See correction below:
///Correction, forgot to add the 's' at the end of both the function and the parent call.
public function getCMSFields(){
$fields = parent::getCMSFields();
}
I can understand an error not being generated in Apache logs for the function because it's legit. But as for the parent call, it should of generated an error since the method don't exists. (Theory: Perhaps, since the function was never actually being called, the parent call wasn't being executed and thus no errors [run-time error]).