Is there a better way of structuring a functionality using BLoC? - flutter

Im wrote a bloc for a functionality that has a home page (that fetches a list of items) and a create/edit page (that creates/updates items).
My bloc is currently in charge of handling both pages since they belong to the same functionality, but my code is getting a little bit harder to read/write then usual because im handling 2 states at once.
class MeusSellersState extends Equatable {
const MeusSellersState({
this.home,
this.form,
});
// Home and Form are my two states.
final Home home;
final Form form;
MeusSellersState copyWith({
Home home,
Form form,
}) {
return MeusSellersState(
home: home ?? this.home,
form: form ?? this.form,
);
}
#override
List<Object> get props => [home, form];
}
I know i could separate this bloc in two, but im worried that more complex functionalities could make me write several blocs and pollute even more my code.
Does anyone know a better solution ?

Related

Where to put bloc providers in flutter

I have tried to use bloc in flutter for a period, but the question of where to put bloc provider confuse me.
At the beginning of learning flutter, I put nearly all the providers in the main() function by using MultipleBlocProvider.
But due to some of my blocs subscribe to streams such as firebase snapshots, I think it might cost extra resources (I am not sure but at least some memory was occupied I guess) if I do not close those subscriptions by closing the bloc.
However, if the provider is in the main(), change page or pop up would not close these blocs and the subscriptions inside.
In this scenario, I try to put some bloc providers into specific pages, so the bloc can be close and recreate when I goes in and out of this page. But there are some other questions occurs.
For example, if I have a bloc called ProductDetailsBloc which is used to control the widget of product details in product details page, its events contains an event called GetProductBySku which is used to get product from firebase and set the product inside the state(ProductDetailsState).
I want to put this bloc and its provider inside product details page and put an event trigger inside product list widget (located in product list page) and its onTap() function, where users click on the product list item (this is a thumbnail product item which is from another resources and only contains very basic info such as sku, title ) inside product list page and then navigator to product details page to view the full information of this product.
But as I mentioned before, If I put the bloc provider of ProductDetailsBloc inside product details page, I can not use GetProductBySku before entering product details page
So, I personally have two ideas for this questions,
the first one is passing the product sku through arguments to product details page and then call the GetProductBySku after the bloc has been created.
the second one is the put the ProductDetailsBloc inside the main(), so I can skip this questions, and directly use the GetProductBySku in product list widget, but this turns the problems in to the very front.
I do not know which one is better, so I would be very appreciative if someone can give some suggestions.
And back to the main questions, what is the best practice of putting bloc providers and what are the concerns if I put providers inside main().
Thanks for every one that read to here because this is a bit long and this is my first time of asking on attack overflow :3
You can find more discussion regarding this in the discord server of Bloc. More info: https://discord.com/channels/649708778631200778/649708993773699083/860604230930006016
I will copy paste the message and code here. we have been using bloc
in our app for over a year and it seems we have got an issue.
Scenario: List of Posts needs to be shown in home feed. Let's say each
post is encapsulated in PostBloc (operations on post to be performed
here). I have been using moor to reactively find if anything changes
in the data (like number of likes of post which might have trigged
from post detail page where post detail was fetched again). I want to
know if it is a wise decision to create BlocFactory which will be
providing Bloc object (here different PostBloc object) based on
Post_Id.
I have written a sample BlocFactoryProvider, please let me know your
thoughts
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
typedef CreateIfAbsent<T> = T Function();
//create bloc of <T> type
//author: https://github.com/imrhk
class BlocFactory<T extends Bloc<dynamic, dynamic>> {
Map<String, T> _cache = <String, T>{};
T createBloc({#required String id, #required CreateIfAbsent<T> createIfAbsent}) {
assert(id != null && createIfAbsent != null);
if (_cache.containsKey(id)) {
return _cache[id];
}
final value = createIfAbsent.call();
_cache[id] = value;
return value;
}
void dispose() {
_cache.values.forEach((bloc) => bloc?.close());
_cache.clear();
}
}
class BlocFactoryProvider<T extends Bloc<dynamic, dynamic>, V>
extends SingleChildStatelessWidget {
final BlocFactory<T> _blocFactory;
final Widget child;
BlocFactoryProvider({BlocFactory<T> blocFactory, this.child})
: _blocFactory = blocFactory ?? BlocFactory<T>();
#override
Widget buildWithChild(BuildContext context, Widget child) {
return InheritedProvider<BlocFactory<T>>(
create: (_) => _blocFactory,
dispose: (_, __) => _blocFactory.dispose(),
child: child,
lazy: false,
);
}
static BlocFactory<T> of<T extends Bloc<dynamic, dynamic>>(BuildContext context) {
try {
return Provider.of<BlocFactory<T>>(context, listen: false);
} on ProviderNotFoundException catch (e) {
if (e.valueType != T) rethrow;
throw FlutterError(
"""
BlocProvider.of() called with a context that does not contain a Bloc of type $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: $context
""",
);
}
}
Felix's replay was
It’s hard to say without more context but I’d recommend moving the
caching to the repository layer and instead have a single PostBloc
with PostChanged event that pulls the latest details (using cache).
So he did not completely discarded the idea.
declaration: I am the author of the below code shared over discord in March'21.

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

reusable classes in flutter

I have a page like the following. This page has two buttons. One of them is sending the form to the relevant place. In my case to the instructor. But, this one is not important for now. The second button is the important one. Anyway, the second one is saving the form as a draft. So, I know I need to save the data that is entered, in SQLite. Then when he/she want to edit the form again I need to pull the data from SQLite then fill the relevant places. Here is the question. When I click to edit button. The page below will show up with the content that is filled before. However, for this task, it does not make sense to code again the screen below for the edit button. So I need to reuse the page below. How can I do it? Any idea. Is there any content that could be helpful? Please attach a link, video, etc. It doesn't matter. I just want to learn how to do it. I hope I could explain the situation. If any more information is required to understand the situation feel free to ask for it.
There are many ways to achieve what you need. But basically, you need to check whether there is saved data or not? If there is saved data then show it otherwise show an empty page. Simple example:
class MyPageData {
String firstField;
String secondField;
// ... fields with page info
}
class MyFormPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyForPageState();
}
class _MyForPageState extends State<MyFormPage>{
MyPageData _savedData;
#override
void initState() {
super.initState();
_savedData = loadDataFromDb();
}
#override
Widget build(BuildContext context) {
String myFirstField = "";
String mySecondField = "";
// other fields
if (_savedData != null){
myFirstField = _savedData.firstField;
mySecondField = _savedData.secondField;
// other fields
}
// render page with fields values above
}
}
Here MyPageData is a model of all the data on your page. So it's easier to work with. Also, this data is saved to db and restored from db in the future.
MyFormPage is a stateful widget for your form page. There is a loadDataFromDb method that is used to load saved data. Then in the build method, we check whether there is saved data or not. If there is data we filled initial values for all fields on our page and use those fields as initial values for the widgets from which our page is constructed. So if there is no data saved then _savedData will be null and all widgets will have empty initial values.
If you put your page in a widget like this
class MyPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: // your page here
)
}
}
you can open it in different locations.
_onButton1Click(BuildContext context){
// do something button 1 specific
final route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
}
_onButton2Click(BuildContext context){
// do something button 2 specific
final route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
}

Flutter best practices share data between screen

I'm new in flutter and I read lot of documentations on multiple subjets. One of them is sharing data between screens. I found a lot of solutions and as I'm creating a base project with lot of features, I want to know which one is the best
Solution 1 : In constructor
Basically, when we navigate, we send the data through the next constructor widget.
Solution 2 : In session
Creates a singleton which can be access from everywhere in the application and each time you need to send data, you add it in the session and the next widget can retrieve it.
Solution 3 : In blocs
I read this solution which looks good:
I made a BLoC container class where I've instantiated the BLoCs of the two screen. Then here I set the reference from the BLoC A to a stream of the BLoC B where I want to send the data. The BLoCs are still decoupled cause they don't know anything about each other, the BLoC A doesn't get passed through the constructor on the BLoC B and vice versa, but it just knows that it will receive some data on one of its streams.
UPDATED:
Solution 4 : Inherited widget
With a static function like :
static InheritedDataProvider of(BuildContext context) => context.inheritFromWidgetOfExactType(InheritedDataProvider);
}
So you can access to the data initialized in the parent with something like :
final data = InheritedDataProvider.of(context).data;
Maybe there are others solutions and I'll be glad to know them.
Thanks
The best way is passing a parameter to the constructor
Navigator.push(
context,
PageTransition(
type: PageTransitionType.fade,
child: LoginPage(
userModel: model,
)));
then :
class LoginPage extends StatefulWidget {
LoginPage({this.userModel});
User userModel;
#override
_LoginPageState createState() => _LoginPageState(userModel: userModel);
}
class _LoginPageState extends State with TickerProviderStateMixin {
User userModel;
_LoginPageState({this.userModel});
}
}
What I use:
Call next screen with Navigator.pushNamed(ctx, '/foo', arguments: bar). Only include arguments if needed (e.g. for "update detail" screen).
Centralize all possible routes in the widget that contains MaterialApp.
Pass a clone of the passed argument to the next screen (immutable data).
In code:
...
MaterialApp(
onGenerateRoute: (s) => MaterialPageRoute(builder: (ctx) => _route(s), settings: s),
)
...
Widget _route(RouteSettings route) {
switch (route.name) {
case '/signup':
return SignupRoute();
case '/vendor':
return VendorRoute((route.arguments as Vendor)?.data?.clone());
default:
throw ('No match for ${route.name}');
}
}

How to provide data with provider using nested data models [List > Item > Sublist > SubItem]

i started using Provider package for state management, and using it in a basic way.
As the app gets more complex i want to extend the usage.
Now i have this model structure in mind: List<Client> having a List<Product> (and deeper having a List<Component>).
I have a MultiProvider using a ChangeNotifierProvider for the Clients, means the List<Client> is managed by the provider, so far so good.
Now i want to directly use the List<Product> in a provider, or later the List<Component> inside the List<Product>. I do not want to go the way through the List<Client>...down to the Component.
Here i have an image map of the structure to visualize.
Here is some simplified code:
// Just an example idea of..
Class Product with ChangeNotifier {
final String title;
}
Class Client with ChangeNotifier {
final String name;
final String List<Product>;
}
Class Clients with ChangeNotifier {
final List<Client> _items;
}
void main() {
// start the app
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Clients()),
// How to provide a List<Product> that actually in the model
// belongs to a Client in the List<Client>
],
child: MaterialApp(
body: ...
)
);
}
}
So the main question is how to provide a List<Product> that actually in the model
belongs to a Client in the List<Client>?
Thanks for your help in the comments.
There seem to be two possible solutions to it.
Do not use nested structure. This is possible by keeping the id of the parent inside the before nested list and throw it out the of the parent list. Then give it an own ChangeNotifierProvider and filter the items by the parent id when needed.
So in my example List and List are on the same level and both have an
ChangeNotifierProvider. The product model holds the id of it's client and the list has an method getbyClientId(clientId) that results in a filtered productslist to the specified client. That's it.
Use ChangeNotifierProxyProvider to delegate the request into the nested list.
I have chosen the first way as in my case it seemed the right way. And after working some time now with it i can not say it was a wrong decision, so i have my freedom with it.
Probably for other cases the second approach might be better. I haven't invested too much time in ChangeNotifierProxyProvider, so please check other sources for more details on that.