How to access a variable present in state in cubit in flutter? - flutter

I am using cubit for state management in my app. I have a variable in the state called prices which I want to access:
Future<void> fetchMonthlyTotals(String userId) async {
//var userId = await getUserId();
var prices =
await myDB().getPrices(userId);
print(prices .toString());
// ignore: unnecessary_null_comparison
if (prices != null && prices .isNotEmpty) {
emit(MonthlyTotalsState(monthlyTotals: prices ));
} else {
debugPrint("prices is empty");
}
}
This is the class where I want to access prices variable:
void getExpenses() async {
var prices = //get price from cubit
print(prices);
}
Both the codes are in different classes and are present in the same package
How do I access the price variable?
Kindly comment if more information is needed.

You can use like this aswell
BlocBuilder<Cubit, CubitState>(
buildWhen: (previous, current) => current is MonthlyTotalsState
builder: (context, state) {
if(state is MonthlyTotalsState){
return Center(
child: Text('Monthly TotalPrices:${state.monthlyTotals}'),
);
}
return const SizedBox();
},
),

If you need to use this value inside the UI you should use a BlocBuilder.
It will update your UI whenever the state changes.
If you however want to do something like showing a dialog or navigating in response to a state change you should use a BlocListener. If you want to do a combination of both of the mentioned use-cases you can use a BlocConsumer
BlocBuilder<ReportinCubit, MonthlyTotalsState>(
builder: (_, monthlyTotalState) {
return Center(
child: Text('Monthly Total Prices: ${monthlyTotalState.prices}'),
);
},
),

If you need to use this value inside the UI you should use a CubitBuilder.
It will update your UI whenever the state changes.
CubitBuilder<ReportinCubit, MonthlyTotalsState>(
builder: (_, monthlyTotalState) {
return Center(
child: Text('Monthly Total Prices: ${monthlyTotalState.prices}'),
);
},
),

Related

How to populate DropdownSearch flutter with local database in flutter

I want to populate my DropdownSearch with fetching data from local database. Plese help me.
Future<dynamic> getList() async {
final db = await getDatabase();
final res = await db.rawQuery("SELECT * FROM tb_point_of_Sale");
List<dynamic> list =
res.isNotEmpty ? res.map((c) => PointOfSale.fromJson(c)).toList() : [];
}
body: Column(
children: [
DropdownSearch<String>(
mode: Mode.MENU,
items: PointOfSaleDao.db.getList(),
showSearchBox: true,
label: "Menu mode",
hint: "point of sale in menu mode",
onChanged: (value) {}
),
],
),
As getList returns a Future, use a FutureBuilder to draw the widget for each state the Future can be, whether it's still loading or finished fetching the data. The FutureBuilder will provide you with an AsyncSnapshot. After the Future has been resolved, you should be able to access the loaded items by calling .data on the AsyncSnapshot.
FutureBuilder(
future: getList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
snapshot.data // access the loaded list
} else if (snapshot.hasError) {
snapshot.error // access the error message
} else {
// the future is still resolving
}
}
)
In case you are using the dropdown search package, there seems to be a field for that named asyncItems on it you might want to look into.

how to let consumer listen to multiple parameters in flutter?

I need to let the consumer widget listen to multiple variables depending on a boolean value.
this is the model class
class Lawyer{
Data? data;
double? distance = 0;
Lawyer({this.data, this.distance});
factory Lawyer.fromJson(Map<String, dynamic> json) =>
Lawyer(data: Data.fromJson(json['listing_data']));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
class Data{
String? title;
String? email;
String? phone;
Location? location;
List<String>? logo;
List<String>? cover;
Data({this.title, this.email, this.phone, this.logo, this.cover, this.location});
factory Data.fromJson(Map<String, dynamic> json) {
var logo = json['_job_logo'];
var cover = json['_job_cover'];
var long = json['geolocation_long'];
var lat = json['geolocation_lat'];
return Data(title: json['_job_tagline'], email: json['_job_email'],
location: Location(latitude: json['geolocation_lat'], longitude: json['geolocation_long']),
phone: json['_job_phone'], logo: List<String>.from(logo),
cover: List<String>.from(cover)
);
}
}
and this is the view model notifier
class LawyerAPIServices extends ChangeNotifier{
final url = "https://dalilvision.com/wp-json/wp/v2/job_listing";
List<Lawyer> lawyersList = [];
List<Lawyer> staticLawyersList = [];
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = lawyersList;
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
Future<List<Lawyer>> getFullListOfLawyers() async {
notifyListeners();
print('fulll list: ${staticLawyersList.length}');
return staticLawyersList;
}
}
and finally this is the consumer widget
Consumer<LawyerAPIServices>(
builder: (context, value, child) => FutureBuilder(
future: _list,
builder: (BuildContext context, AsyncSnapshot<List<Lawyer>> snapshot) {
if (snapshot.hasData){
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) => const Divider(color: Colors.transparent),
itemCount: value.lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: snapshot.data![index].data!.title!,
email: snapshot.data![index].data!.email!,
phone: snapshot.data![index].data!.phone!,
logo: snapshot.data![index].data!.logo![0],
cover: snapshot.data![index].data!.cover![0]
),
);
}
}
);
}
else if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString())
);
}
else {
return const CircularProgressIndicator(
strokeWidth: 2,
);
}
},
),
)
In the notifier class there are two lists, the staticLawyerList is initialized only once when getting the list from a network call and then used as a backup list, and the lawyersList is the one that will be manipulated.
what I have done until now is to get the initial value of lawyersList by a network call, then somehow the staticLawyersList values are always equal to lawyersList, even if I made any change or manipulate the lawyersList these changes will automatically reflect on the staticLawyersList which is really weird.
now what I want to achieve exactly is to apply a condition to update the UI with the appropriate list depending on this condition.
if(setByPosition == false){
//update UI with `staticLawyersList`
}
else {
//update UI with `lawyersList`
}
update!!!!!!!!
here's how I update my consumer
CheckboxListTile(
activeColor: Colors.black,
value: isChecked,
onChanged: (value) async {
saveSharedPreferences(value: value!);
if(value == true) {
Provider.of<LawyerAPIServices>(context, listen: false).sortLawyersList(
devicePosition: widget.position, lawyersList: widget.list);
}
else{
Provider.of<LawyerAPIServices>(context, listen: false).getFullListOfLawyers();// the list returned by this function don't applied to the consumer
}
setState(() {
isChecked = value;
Navigator.pop(context);
});
},
title: const Text('Filter by distance'),
),
A few things to consider:
When you do this "staticLawyersList = lawyersList" you actually have two "pointers" to the same list. It works that way for lists, sets, classes, etc.. only basic types as int, double, string are really copied.
You can use this instead: "staticLawyersList = List.from(lawyersList);"
It doesn't seem you need the ChangeNotifier in your LawyerAPIServices. You could create an instance of LawyerAPIServices in the widget you need it and call fetchLawyers. Do it in the initState of a StatefullWidget if you don't want the list to be rebuilt multiple times. In your build method use a FutureBuilder to read the Future and decide what to show in the UI.
class _MyWidget extends State<MyWidget> {
late final LawyerAPIServices lawyerApi;
// Create this variable to avoid calling fetchLawers many times
late final Future<List<Lawyer>> lawyersList;
#override
void initState() {
super.initState();
// Instantiate your API
lawyerApi = LawyerAPIServices();
// This will be called only once, when this Widget is created
lawyersList = lawyerApi.fetchLawyers();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lawyer>>(
future: lawyersList,
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerApi.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
);
}
Widget _listView(List<Lawyer> lawyersList) {
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) =>
const Divider(color: Colors.transparent),
itemCount: lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: lawyersList[index].data!.title!,
email: lawyersList[index].data!.email!,
phone: lawyersList[index].data!.phone!,
logo: lawyersList[index].data!.logo![0],
cover: lawyersList[index].data!.cover![0]),
);
});
}
}
If for any reason you need to share the same LawyerAPIServices across multiple widgets, you could instantiate it on the top of your tree and send it down using Provider or as a parameter.
The method getFullListOfLawyers doesn't need to return a Future, since staticLawyersList is a List (not a Future). You could get this list directly using "LawyerAPIServices.staticLawyersList" or maybe something like this could make sense:
Future<List> getFullListOfLawyers() async {
if(staticLawyersList.isEmpty) {
await fetchLawyers();
}
print('fulll list: ${staticLawyersList.length}');
return Future.value(staticLawyersList);
}
as #Saichi-Okuma said that to copy the content of a list you should use staticLawyersList = List.from(lawyersList) because in dart and most of the java compiler programming languages when you use staticLawyersList = lawyersList this means that you are referring to the lawyersList by the staticLawyersList.
then I manipulate the lawyersList as I want with help of staticLawyersList
lawyersList.clear();
lawyersList.addAll(staticLawyersList);
But when I did so, the consumer didn't apply the changes based on the staticLawyersList although the logcat shows that the staticLawyersList length is 10 which is what I want (full list without filtration).
the conclusion of my problem can be listed in two points:
1- the consumer is listening to only one list lawyersList and I think it still exists.
2- the pointer problem as #Saichi-Okuma mentioned.
here are the full code changes
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = List.from(lawyersList);// use this statment instead of staticLawyersList = lawyersList
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
The Consumer Widget gets rebuild every time you call notify notifyListeners, regardless the state of any lists.
Maybe you are not accessing the Instance of the API being consumed. Make sure you are using the 2nd parameter of the Consumer builder.
Consumer<LawyerAPIServices>(builder: (context, lawyerAPI, child) =>
FutureBuilder(
future: lawyerAPI.fetchLawyers(),
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerAPI.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
I don't think you need the code below for this particular need. It'd override your lawyersList and notify to all listeners even though nothing really changed. Just access your staticLawyersList directly, since it was populated when you called fetchLawyers.
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}

How to wait multiple bloc events in same bloc

I have one bloc with multiple events. Here I load categories and locations and wait using BlocListener. But my condition for show circular progress indicator work incorrectly and after load categories and locations also shows. How I can use bloc correctly in this case?
Code
apiDataBloc.add(LoadCategoriesEvent());
apiDataBloc.add(LoadLocationsEvent());
------------------------
return BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, state) {
if (state is CategoriesLoaded) {
categories = state.categories;
print("Categories loaded");
print(categories.length);
}
},
child: BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, s) {
if (s is LocationsLoaded) {
locations = s.locations;
print("Locations loaded");
print(locations.length);
}
},
child: locations != null &&
categories != null &&
categories.length > 0 &&
locations.length > 0
? Container(child: Center(child: Text('Categories and locations loaded!')))
: Container(child: Center(child: CircularProgressIndicator())),
),
);
I tried also like this but doesn't work.
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is LocationsLoaded) {
print("Locations loaded");
locations = state.locations;
print(locations.length);
return BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, s) {
if (s is CategoriesLoaded) {
print("Categories loaded");
categories = s.categories;
print(categories.length);
return Container(
child: Center(
child: Text('Categories and locations loaded!')));
}
return Container(
child: Center(child: CircularProgressIndicator()));
},
);
}
return Container(child: Center(child: CircularProgressIndicator()));
},
),
);
You should create one state DataLoaded with 2 fields categories and locations
Something like that:
class DataLoaded extends ApiDataState {
const DataLoaded(
this.categories,
this.locations,
);
final List<Type> categories;
final List<Type> locations;
#override
String toString() => 'DataLoaded';
}
Then you need to fetch data from API in the ApiDataBloc class:
class ApiDataBloc extends Bloc<YourEventType, ApiDataState> {
ApiDataBloc() : super(YourInitialState());
#override
Stream<ApiDataState> mapEventToState(YourEventType event) async* {
if (event is YourFetchApiEvent) {
yield YourLoadingState();
final categories = await _fetchCategories();
final locations = await _fetchLocations();
yield DataLoaded(categories,locations);
}
}
}
and the final step is BlocBuilder in your widget:
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is YouLoadingState) {
return Center(child: CircularProgressIndicator());
}
if (state is DataLoaded) {
print(state.locations);
print(state.categories);
return Center(
child: Text('Categories and locations loaded!'),
);
}
},
),
);
I would place the logic into the bloc. If I understand correctly, you get an event triggered as soon as the data is loaded. Then you could create 2 variables in the bloc bool categoriesLoaded, locationsLoaded which you set true upon the event. In mapEventToState you could forward from each of those event mappers to a common event mapper that checks if both variables are true and sends the proper state then. An inProgress state could display which of the data streams has already been loaded.
I know what you meant.
Example Case:
#some_bloc.dart (not in event or state file)
on<someEventNo1>((......) =>
emit(LoadingState());
emit(EmitResultAPI());
on<someEventNo2>((......) =>
emit(LoadingState());
emit(someState());
#main.dart
someMethod() {
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
BlocProvider.of<SomeBloc>(context).add(someEventNo2());
}
If you do your code like that, bloc builder will not catch state change when someEventNo1 emits EmitResultAPI, because you are sending 2 consecutive BlocProvider.of<>().
Solution:
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
Future.delayed(Duration(miliseconds: 100)).then((valueFuture) => BlocProvider.of<SomeBloc>(context).add(someEventNo2()));

Fixing Issues with FutureBuilder

In my Flutter project, I am trying to implement a button click event by using FutureBuilder. Basically when the button clicked, it supposed to get the data and display in a table. So my button onPressed event handling is as below:
onPressed: () async{
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
var p = double.parse(loanAmount);
var r = double.parse(interestRate);
var n = int.parse(monthes);
Api api = new Api();
new FutureBuilder<List>(
future: api.calculateEmi(p, r, n),
builder: (BuildContext buildContext, AsyncSnapshot<List> snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
print( snapshot.data);
return new SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: DataTableWidget(listOfColumns: snapshot.data.map(
(e)=>{
'Month': e['Month'],
'Principal': e['Principal'],
'InterestP': e['InterestP'],
'PrinciplaP': e['PrinciplaP'],
'RemainP': e['RemainP']
}).toList()
),
);
}
}
);
}
}
The Api call is working and the method calculateEmi is called and get data returned ( a List of Map), but the view just not updated and no table appeared at all, and I use breakpoint at the builder portion but it never go into it, where did I do wrong, can anyone help? thanks.
The FutureBuilder needs to be inserted somewhere in the flutter widget tree. Simply creating a new FutureBuilder doesn't tell flutter what to do with it.
I'm guessing you instead want to put the FutureBuilder you created somewhere in the parent widget of the onPressed method. If you need it to only show when the button is pressed you can do that with a bool that determines whether to show the FutureBuilder or not.
Ex.
Widget build(context) {
if(buttonPressed) {
return FutureBuilder(
...
);
}
else {
return Container();
}
}

How to run a function inside a child stateful widget from parent widget?

I am trying to run a function(with arguments) inside two-levels down StateFul widget, by clicking a button in the parent of the parent of that child(after having all widgets built, so not inside the constructor). just like in the image below:
More details is that I created a Carousal which has Cards inside, published here.
I created it with StreamBuilder in mind(this was the only use case scenario that I used it for so far), so once the stream send an update, the builder re-create the whole Carousal, so I can pass the SELECTED_CARD_ID to it.
But now I need to trigger the selection of the carousal's Cards programmatically, or in another word no need for two construction based on the snapshot's data like this:
return StreamBuilder(
stream: userProfileBloc.userFaviourateStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return SelectableCarousal(
selectedId: snapshot.data.toInt(),
onTap: //Update the stream
//some props...,
);
} else {
return SelectableCarousalLoading(
selectedId: null,
onTap: //Update the stream
//some props...,
);
}
},
);
But instead, I'm trying to have something like this so I can use it for others use cases:
Widget myCarousal = SelectableCarousal(
selectedId: null,
onTap: //Update the stream
//some props...,
);
return StreamBuilder(
stream: userProfileBloc.userFaviourateStream,
builder: (context, snapshot) {
// Then when data ready I can update
// the selection by calling two-level down function
if (snapshot.hasData) {
myCarousal.selectById(3);
}
// Build same carousal in all cases.
return myCarousal;
},
);
so this led me to my original question "How to run a function(with arguments) inside two-levels down StateFul widget?".
I appreciate any help. thanks a lot.
I was able to solve that challenge using the BLoC & Stream & StreamSubscription, see the image below:
Inside the Homepage screen:
///...Inside Homepage screen level-0
RaisedButton(
child: Text('Update value in the BLoC'),
onPressed: () {
bloc.changeSelectedState(isSel);
},
),
//...
inside the BLoC:
class Bloc {
final BehaviorSubject<bool> _isSelectedStreamController = BehaviorSubject<bool>();
// Retrieve data from stream
Stream<bool> get isSelectedStream => _isSelectedStreamController.stream;
// Add data to stream
Function(bool) get changeSelectedState => _isSelectedStreamController.sink.add;
void dispose() {
_isSelectedStreamController.close();
}
}
final bloc = Bloc();
Inside any widget in any level as long as it can reach the bloc:
// This inside the two-levels down stateful widget..
StreamSubscription isSelectedSubscription;
Stream isSelectedStream = bloc.isSelectedStream;
isSelectedSubscription = isSelectedStream.listen((value) {
// Set flag then setState so can show the border.
setState(() {
isSelected = value;
});
});
//...other code
#override
Widget build(BuildContext context) {
return Container(
decoration: isSelected
? BoxDecoration(
color: Colors.deepOrangeAccent,
border: Border.all(
width: 2,
color: Colors.amber,
),
)
: null,
//...other code
);
}
so the new design of my widget includes the BLoC as a main part of it, see the image:
and...works like a charm with flexible and clean code and architecture ^^