Flutter Riverpod context.read(providerref) doesn't give same reference of stateNotifier when call in two different functions of a Widget - flutter

I am setting values of variables declared in View Model class which is a StateNotifier, inside a function of widget. When I try to access the values of those variables from a different function of same widget, their values are null. I have debugged code to verify that first function is setting values correctly.
Any help will be highly appreciated.
Here is cutdown version of my StateNotifier
class ProductViewModel extends StateNotifier<ProductState> {
String errorMessage;
Product product;
final ProductService productService;
final CategoryService categoryService;
final BrandService brandService;
final TranslatorService translatorService;
final ProductOptionsViewModel productOptionsViewModel;
final ProductVariantViewModel productVariantViewModel;
ProductViewModel(this.productService, this.categoryService, this.brandService, this.translatorService,
this.productOptionsViewModel, this.productVariantViewModel)
: super(ProductInitial());
String productId;
List<SizeEnum> _sizes;
String _selectedBrand;
String _selectedCategory;
String _selectedStore;
String _productName;
String _productIntlName;
String _sku;
String get selectedBrand => _selectedBrand;
set selectedBrand(String value) {
_selectedBrand = value;
}
String get selectedCategory => _selectedCategory;
set selectedCategory(String value) {
_selectedCategory = value;
}
String get selectedStore => _selectedStore;
set selectedStore(String value) {
_selectedStore = value;
}
String get productName => _productName;
set productName(String value) {
_productName = value;
}
String get productIntlName => _productIntlName;
set productIntlName(String value) {
_productIntlName = value;
}
String get sku => _sku;
set sku(String value) {
_sku = value;
}
Future<bool> saveProductDetails() async {
bool isSave = false;
bool imageSaved = await saveProductImage();
if (!imageSaved) return imageSaved;
List<String> searchKeywords = indexProductName(_productName);
List<String> searchTag1 = _searchTag1 != null ? indexProductName(_searchTag1) : null;
List<String> searchTag2 = _searchTag2 != null ? indexProductName(_searchTag2) : null;
List<String> searchTag3 = _searchTag3 != null ? indexProductName(_searchTag3) : null;
if (deal != null && _dealsAddedDateTime == null) {
_dealsAddedDateTime = DateTime.now();
}
print(productOptionsViewModel.toString());
Product _product = Product(
productId: productId,
name: _productName,
intlName: _productIntlName,
category: FirebaseFirestore.instance.doc(_selectedCategory),
brand: FirebaseFirestore.instance.doc(_selectedBrand),
sku: _sku,
quantity: _quantity,
price: _price,
containSizes: productOptionsViewModel.sizes,
containColors: productOptionsViewModel.colors,
accessory: productOptionsViewModel.accessory ,
salesTaxApplicable: productOptionsViewModel.salesTax,
);
isSave = await productService.saveProduct(_product);
if (!isSave) {
errorMessage = "Error in saving product information";
} else {
productId = productService.newProductId;
}
return isSave;
}
}
StateNotifierProvider declaration
final productViewModelProvider = StateNotifierProvider.autoDispose<ProductViewModel,ProductState>((ref) => ProductViewModel(
ref.watch(productServiceProvider),
ref.watch(categoryServiceProvider),
ref.watch(brandServiceProvider),
ref.watch(translatorServiceProvider),
ref.watch(productOptionsViewModelProvider),
ref.watch(productVariantViewModelProvider)));
UI Functions
I set values in validateProduct and read values again in saveProductDetails.
Future<bool> validateProduct() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
final model = context.read(productViewModelProvider.notifier);
final storeViewModel = context.read(storeViewModelProvider.notifier);
var _store = await storeViewModel.getMyStore();
model.productName = _productName;
model.productIntlName = _productIntlName;
model.sku = _sku;
model.quantity = _quantity;
model.price = _price;
model.selectedBrand = _selectedBrand;
model.selectedCategory = _selectedCategory;
model.selectedStore = _storeCode;
model.description = _productDescription;
model.manufacturerLink = _manufacturerLink;
model.searchTag1 = _searchTag1;
model.searchTag2 = _searchTag2;
model.searchTag3 = _searchTag3;
if (model.addedDateTime == null) {
model.addedDateTime = DateTime.now();
}
if (_storeCode == null) {
_storeCode = _store.store;
}
if (model.selectedStore == null) {
model.selectedStore = _storeCode;
}
return true;
}
else return false;
}
Future<bool> saveProductDetails() async {
final model = context.read(productViewModelProvider.notifier);
bool isProductSaved = await model.saveProductDetails();
if (isProductSaved) {
if (_isProductExist) {
displayMessage(context, "Product Information Updated");
} else
displayMessage(context, "Product Information Saved");
_formSaved = true;
isFormChanged = false;
return true;
} else if (isProductSaved == false) {
_formSaved = false;
displayMessage(context, model.errorMessage);
}
return isProductSaved;
}
State
abstract class ProductState {
const ProductState();
}
class ProductInitial extends ProductState {
const ProductInitial();
}
class ProductLoading extends ProductState {
const ProductLoading();
}
class ProductLoaded extends ProductState {
final Product product;
ProductLoaded(this.product);
#override
bool operator ==(Object other) =>
identical(this, other) || other is ProductLoaded && runtimeType == other.runtimeType && product == other.product;
#override
int get hashCode => product.hashCode;
}
class ProductSaving extends ProductState {
const ProductSaving();
}
class ProductSaved extends ProductState {
final Product product;
ProductSaved(this.product);
#override
bool operator ==(Object other) =>
identical(this, other) || other is ProductSaved && runtimeType == other.runtimeType && product == other.product;
#override
int get hashCode => product.hashCode;
}
class ProductError extends ProductState {
final String errorMessage;
ProductError(this.errorMessage);
}

Remove .autoDispose modifier
final productViewModelProvider = StateNotifierProvider<ProductViewModel,ProductState>((ref) => ProductViewModel(
ref.watch(productServiceProvider),
ref.watch(categoryServiceProvider),
ref.watch(brandServiceProvider),
ref.watch(translatorServiceProvider),
ref.watch(productOptionsViewModelProvider),
ref.watch(productVariantViewModelProvider)));

Related

Why if I modify a T object property all instances project of T object are modified in Dart?

I have two provider classes where as a property there is an instance of a Combo object.
My problem is when I modify value properties of Combo object of provider one, the instance of provider two is modified as well.
This is a problem for me because it makes me impossible to have two different instances even when they are created on different classes.
#immutable class Combo
{
Combo(
{
this.idCombo = 0,
this.idThirdCombo = 0,
this.name = '',
this.urlImage = '',
final List<int>? recipy,
final List<Product>? selectedRecipy,
final List<OptionalRecepy>? optionalRecepy,
final SwitStoreProductType? type,
}) :
this.recipy = recipy ?? [],
this.selectedRecipy = selectedRecipy ?? [],
this.optionalRecepy = optionalRecepy ?? [],
this.type = type ?? SwitStoreProductType.none;
final int idCombo;
final int idThirdCombo;
final String name;
final String urlImage;
final List<int> recipy;
final List<Product> selectedRecipy;
final List<OptionalRecepy> optionalRecepy;
final SwitStoreProductType type;
}
//Provider One
class ProductDetailsBSProvider extends ChangeNotifier
{
Combo? _currentCombo;
void modifyCombo(Product product, int index)
{
if(index != this._currentOptionIndex)
{
if(this._currentCombo!.selectedRecipy.length > 1)
{
int previousIndex = (this._currentCombo!.selectedRecipy.length - 1);
this._currentCombo!.selectedRecipy.removeAt(previousIndex);
this._currentCombo!.selectedRecipy.insert(previousIndex, product);
this._currentOptionIndex = index;
}
else
{
this._currentCombo!.selectedRecipy.add(product);
this._currentOptionIndex = index;
}
}
else
{
if(this._currentCombo!.selectedRecipy.length == 0)
{
this._currentCombo!.selectedRecipy.add(product);
}
else
{
this._currentCombo!.selectedRecipy.removeLast();
this._currentCombo!.selectedRecipy.add(product);
}
}
notifyListeners();
}
}
//Provider Two
class StoreProvider extends ChangeNotifier
{
Combo? _currentCombo;
}
If I print the _currentCombo properties value of Provider Two it will be exactly the same as Provider One

Firestore collection map to list

Hi I need to retrieve all documents from firestore collection with this:
EventList<Event>testdata(QuerySnapshot snapshot) {
return snapshot.docs.map((data) => EventList<Event>(events: {
data['date']: [
Event(
date: data['date'], title: data['name'], icon: Icon(
Icons.block,
color: Colors.red[200],
size: 30,
)),
]
})).toList();
}
Stream<EventList<Event>> get caldendardata {
return events.snapshots().map(testdata);
}
but i get this error: A value of type 'List<EventList<Event>>' can't be returned from the method 'testdata' because it has a return type of 'EventList<Event>'.
The Firestore :
I'm using this package to add calendar to my app it requires the event on the calendar to be {EventList<Event>? markedDatesMap} .
EventList form the package:
class EventList<T> {
Map<DateTime, List<T>> events;
EventList({
required this.events,
});
void add(DateTime date, T event) {
final eventsOfDate = events[date];
if (eventsOfDate == null)
events[date] = [event];
else
eventsOfDate.add(event);
}
void addAll(DateTime date, List<T> events) {
final eventsOfDate = this.events[date];
if (eventsOfDate == null)
this.events[date] = events;
else
eventsOfDate.addAll(events);
}
bool remove(DateTime date, T event) {
final eventsOfDate = events[date];
return eventsOfDate != null ? eventsOfDate.remove(event) : false;
}
List<T> removeAll(DateTime date) {
return events.remove(date) ?? [];
}
void clear() {
events.clear();
}
List<T> getEvents(DateTime date) {
return events[date] ?? [];
}
}
Event form the package:
class Event implements EventInterface {
final DateTime date;
final String? title;
final Widget? icon;
final Widget? dot;
final int? id;
Event({
this.id,
required this.date,
this.title,
this.icon,
this.dot,
});
#override
bool operator ==(dynamic other) {
return this.date == other.date &&
this.title == other.title &&
this.icon == other.icon &&
this.dot == other.dot &&
this.id == other.id;
}
#override
int get hashCode => hashValues(date, title, icon, id);
#override
DateTime getDate() {
return date;
}
#override
int? getId() {
return id;
}
#override
Widget? getDot() {
return dot;
}
#override
Widget? getIcon() {
return icon;
}
#override
String? getTitle() {
return title;
}
}
abstract class EventInterface {
DateTime getDate();
String? getTitle();
Widget? getIcon();
Widget? getDot();
int? getId();
}
I would appreciate a little help here.
Thank you in advance
map returns a List. That's why you have a List<EventList>.
I believe you are trying to flatten the list so that you instead have a single EventList with all of the events. One way to accomplish this is to use fold.
Here is an example that you should be able to apply to your code. One could paste this into Dartpad to quickly see how it works:
class Event {
const Event(this.id);
final int id;
}
class EventList {
const EventList({required this.events});
final List<Event> events;
}
class FirebaseData {
const FirebaseData(this.docs);
final List<Event> docs;
}
void main() {
// Simulating your data stream
final FirebaseData snapshot = FirebaseData(List.generate(5, (index) => Event(index)));
// What you are returning from your code currently
final List<EventList> eventListList =
snapshot.docs.map((data) => EventList(events: [data])).toList();
// What you actually want to return from your code
final EventList eventList = eventListList.fold(EventList(events: []),
(previousValue, element) => EventList(events: previousValue.events..addAll(element.events)));
print(eventList.events);
}
When performing toList you getting a List<EventList<Event>> each EventList with one event.
I think you want to get a List<Map> from Firestore to later build your class.
You can achieve that with a code like this.
EventList<Event>testdata(QuerySnapshot snapshot) {
//Get all data
final List<Map> eventListMap = snapshot.docs.map((data) => {
data['date']: [
Event(
date: data['date'], title: data['name'], icon: Icon(
Icons.block,
color: Colors.red[200],
size: 30,
)),
]
}).toList();
//Join to single Map, it should not contain repeated keys (date) as one of them would be lost
final Map eventsMap = eventsData.fold({},(map1, map2) => map1..addAll(map2));
//Return your class
return EventList<Event>(events: eventsMap);
}
Stream<EventList<Event>> get caldendardata {
return events.snapshots().map(testdata);
}
I did not try it and you can rename or change anything.

Get data from firebase to insert in MultiSelectFormField(widgets)

I encountered a problem when I want to retrieve data from the firebase database to insert it into my MultiSelectFormField type form.
When I click on the form the list does not open.
The error is as follows:
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception:
type 'String' is not a subtype of type 'int' of 'index' E/flutter
(27777): #0 new MultiSelectFormField...
(package:multiselect_formfield/multiselect_formfield.dart:73:55)
E/flutter (27777): #1 List.forEach
(dart:core-patch/growable_array.dart:285:8) E/flutter (27777): #2
new MultiSelectFormField..
(package:multiselect_formfield/multiselect_formfield.dart:72:28)
my method to build this form
Widget buildMultiForm(BuildContext context,EndRegisterViewModel e) {
return MultiSelectFormField(
autovalidate: e.autoValidateTag,
titleText: 'Choisissez les dispositifs qui vous intéressent',
validator: (value)
{
if (e.selectedsValueTag == null || e.selectedsValueTag.length == 0) {
return 'Veuillez sélectionner au moins un tag';
}
return null;
},
dataSource: [
e.allTags.map((data){
return {'display': '#$data', 'value': data};
}).toList(),
],
textField: 'display',
valueField: 'value',
okButtonLabel: 'OK',
cancelButtonLabel: 'Annuler',
hintText: 'Choisissez-en un ou plus',
value: e.selectedsValueTag,
onSaved: (value) {
e.autoValidateTag=true;
e.selectedsValueTag = value;
},
);
}
/////////////////////////////////////////////
/////User Model ////////////////////////////
////////////////////////////////////////////
class User {
String uid;
String name;
String surname;
String email;
String city;
String country;
int experience;
String numberTel;
String department;
/// List<String> tags;
String tags;
String reason;
List pictures;
bool isRegistered;
List confirmedMatches;
List receivedMatches;
List sendMatches;
///USER FROM DATA////
/// /// /// //// ///
User.fromData(Map<String, dynamic> data)
: uid = data['uid'],
email = data['email'],
name = data['name'],
surname = data['surname'],
city = data['city'],
country = data['country'],
experience = data['experience'],
numberTel = data['numberTel'],
department = data['department'],
tags = data['tags'],
reason = data['reason'],
pictures = data['pictures'] as List,
isRegistered = data['isRegistered'] as bool ?? false,
confirmedMatches = data['confirmedMatches'] as List,
receivedMatches = data['receivedMatches'] as List,
sendMatches = data['sendMatches'] as List;
Map<String, dynamic> toJson() {
return {
'uid': uid,
'email': email,
'name': name,
'surname': surname,
'city': city,
'country': country,
'experience': experience,
'numberTel': numberTel,
'department': department,
'tags': tags,
'pictures': pictures,
'isRegistered': isRegistered,
'reason':reason,
'confirmedMatches': confirmedMatches,
'receivedMatches': receivedMatches,
'sendMatches': sendMatches,
};
}
////ViewModel//////
//////////////////
////////////////
class EndRegisterViewModel extends BaseModel {
final FirestoreService _firestoreService = locator<FirestoreService>();
final NavigationService _navigationService = locator<NavigationService>();
final AuthService _auth = locator<AuthService>();
final formKey = new GlobalKey<FormState>();
List<String> _allDepartments;
List<String> _allTags;
List<String> get allDepartments => _allDepartments;
List<String> get allTags => _allTags;
List _pictures;
String _name;
String _surname;
String _reason;
String _numberTel;
String _errorMessage;
List _selectedsValueTag;
bool _autoValidateTag = false;
bool get autoValidateTag => _autoValidateTag;
set autoValidateTag(bool newValue) {
if (autoValidateTag != newValue) {
_autoValidateTag = newValue;
notifyListeners();
}
}
List get selectedsValueTag => _selectedsValueTag;
set selectedsValueTag(List newValue) {
if (selectedsValueTag != newValue) {
_selectedsValueTag = newValue;
notifyListeners();
}
}
String _selectedValueDepartment;
String get selectedValueDepartment => _selectedValueDepartment;
set selectedValueDepartment(String newValue) {
if (selectedValueDepartment != newValue) {
_selectedValueDepartment = newValue;
notifyListeners();
}
}
/// CONSTRUCTOR ///
/// /// /// /// ////
EndRegisterViewModel() {
_allDepartments = new List<String>();
_allTags = new List<String>();
}
set pictures(List value) {
_pictures = value;
}
set name(String value) {
_name = value;
}
set surname(String value) {
_surname = value;
}
set reason(String value) {
_reason = value;
}
set numberTel(String value) {
_numberTel = value;
}
String get errorMessage => _errorMessage;
String get reason => _reason;
bool validateAndSave() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
void setErrorMessage(String errorMessage) {
_errorMessage = errorMessage;
notifyListeners();
}
Future updateUser() async {
setBusy(true);
if (validateAndSave()) {
User _user = _auth.currentUser;
_user.name = _name;
_user.surname = _surname;
_user.department = _selectedValueDepartment;
_user.tags = _selectedsValueTag.toString();
_user.reason = _reason;
_user.numberTel = _numberTel;
_user.isRegistered = true;
var result = await _firestoreService.createOrUpdateUser(_user);
if (result == null) {
_auth.currentUser = _user;
_navigationService.navigateTo("/communities");
} else {
setErrorMessage(result);
setBusy(false);
}
} else {
setBusy(false);
}
}
Future<void> initialize() async {
setBusy(true);
if (_allDepartments == null || _allDepartments.isEmpty) {
_allDepartments = await _firestoreService.getAllDepartments(); /// it's work
notifyListeners();
}
if (_allTags == null || _allTags.isEmpty) {
_allTags = await _firestoreService.getAllTags();
notifyListeners();
}
setBusy(false);
}
}
datasource = e.allTags.map((data){
return {'display': '#$data', 'value': data};
}).toList(),
Should be work.
As dataSource holds data of type List (documentation).
Your code :
e.allTags.map((data){
return {'display': '#$data', 'value': data};
}).toList(),
itself creates a List which includes every elements of allTags

Instance of 'Response<dynamic>' flutter Api Consumption

I am trying to make a post request in flutter using chopper. I have made an ApiService.dart file as
a generator file.
import 'package:bindle/Chopper/Models/LoginResponse.dart';
import 'package:chopper/chopper.dart';
part 'ApiService.chopper.dart';
#ChopperApi(baseUrl: 'http://192.168.1.20/bindal/api/v1/user/')
abstract class ApiService extends ChopperService {
#Post(path: "login")
Future<Response<LoginResponse>> doLogin([
#Header('auth_key') String authType,
#Query('email') String email,
#Query('password') String password,
#Query('userType') String userType,
]);
static ApiService create() {
final client = ChopperClient(
// The first part of the URL is now here
baseUrl: 'http://192.168.1.20/bindal/api/v1/user/',
services: [
// The generated implementation
_$ApiService(),
],
interceptors: [
HttpLoggingInterceptor()
],
// Converts data to & from JSON and adds the application/json header.
converter: JsonConverter(),
);
// The generated class with the ChopperClient passed in
return _$ApiService(client);
}
}
And this is my generated file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ApiService.dart';
// **************************************************************************
// ChopperGenerator
// **************************************************************************
class _$ApiService extends ApiService {
_$ApiService([ChopperClient client]) {
if (client == null) return;
this.client = client;
}
final definitionType = ApiService;
Future<Response<LoginResponse>> doLogin(
[String authType, String email, String password, String userType]) {
final $url = 'http://192.168.1.20/bindal/api/v1/user/login';
final Map<String, dynamic> $params = {
'email': email,
'password': password,
'userType': userType
};
final $headers = {'auth_key': authType};
final $request = Request('POST', $url, client.baseUrl,
parameters: $params, headers: $headers);
return client.send<LoginResponse, LoginResponse>($request);
}
}
Next what i Did is i generated a model class called as LoginResponse where I have to fetch the data.
abstract class LoginResponse implements Built<LoginResponse, LoginResponseBuilder> {
int get status;
String get message;
LoginResponse._();
factory LoginResponse([void Function(LoginResponseBuilder) updates]) = _$LoginResponse;
static LoginResponse fromJson(String jsonString){
return serializers.deserializeWith(LoginResponse.serializer, json.decode(jsonString));
}
static Serializer<LoginResponse> get serializer => _$loginResponseSerializer;
}
this is the generated file for the above LoginResponse.dart file using built_value generator
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'LoginResponse.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<LoginResponse> _$loginResponseSerializer =
new _$LoginResponseSerializer();
class _$LoginResponseSerializer implements StructuredSerializer<LoginResponse> {
#override
final Iterable<Type> types = const [LoginResponse, _$LoginResponse];
#override
final String wireName = 'LoginResponse';
#override
Iterable<Object> serialize(Serializers serializers, LoginResponse object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'status',
serializers.serialize(object.status, specifiedType: const FullType(int)),
'message',
serializers.serialize(object.message,
specifiedType: const FullType(String)),
];
return result;
}
#override
LoginResponse deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new LoginResponseBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'status':
result.status = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'message':
result.message = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
}
}
return result.build();
}
}
class _$LoginResponse extends LoginResponse {
#override
final int status;
#override
final String message;
factory _$LoginResponse([void Function(LoginResponseBuilder) updates]) =>
(new LoginResponseBuilder()..update(updates)).build();
_$LoginResponse._({this.status, this.message}) : super._() {
if (status == null) {
throw new BuiltValueNullFieldError('LoginResponse', 'status');
}
if (message == null) {
throw new BuiltValueNullFieldError('LoginResponse', 'message');
}
}
#override
LoginResponse rebuild(void Function(LoginResponseBuilder) updates) =>
(toBuilder()..update(updates)).build();
#override
LoginResponseBuilder toBuilder() => new LoginResponseBuilder()..replace(this);
#override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is LoginResponse &&
status == other.status &&
message == other.message;
}
#override
int get hashCode {
return $jf($jc($jc(0, status.hashCode), message.hashCode));
}
#override
String toString() {
return (newBuiltValueToStringHelper('LoginResponse')
..add('status', status)
..add('message', message))
.toString();
}
}
class LoginResponseBuilder
implements Builder<LoginResponse, LoginResponseBuilder> {
_$LoginResponse _$v;
int _status;
int get status => _$this._status;
set status(int status) => _$this._status = status;
String _message;
String get message => _$this._message;
set message(String message) => _$this._message = message;
LoginResponseBuilder();
LoginResponseBuilder get _$this {
if (_$v != null) {
_status = _$v.status;
_message = _$v.message;
_$v = null;
}
return this;
}
#override
void replace(LoginResponse other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$LoginResponse;
}
#override
void update(void Function(LoginResponseBuilder) updates) {
if (updates != null) updates(this);
}
#override
_$LoginResponse build() {
final _$result =
_$v ?? new _$LoginResponse._(status: status, message: message);
replace(_$result);
return _$result;
}
}
finally i called my api in the login page as
void doLogin(String email, String pass, BuildContext context) async {
try {
final response = await Provider.of<ApiService>(context)
.doLogin("d1d2fe0514f7d5c748c0e7e085b36f74","arpit1692#gmail.com",
"e10adc3949ba59abbe56e057f20f883e","App");
print(response.body);
} catch (e) {
print(e);
}
}
Which ultimately gives me Exception as => Instance of 'Response dynamic'
Please help me for what I am doing wrong.
The reason why the Response returns dyanmic is because the response wasn't serialized to the model you've defined. If LoginResponse is the model that should be used for the response, the LoginResponse class should have fromJson() that should serialize the json response. You can follow this guide to help you manage json serialization.

How to filter list with bloc?

So, here is bloc (ref https://bloclibrary.dev/#/flutterinfinitelisttutorial)
class ProductBloc extends Bloc<ProductsEvent, ProductsState> {
String categoryId, userId;
int limit, offset, type;
http.Client client;
ProductBloc(
{#required this.client,
#required this.categoryId,
#required this.userId,
#required this.limit,
#required this.offset,
#required this.type});
#override
ProductsState get initialState => InitialProductsState();
#override
Stream<ProductsState> mapEventToState(ProductsEvent event) async* {
final currentState = state;
print(event);
print(state);
if (event is FetchProductsEvent && !_hasReachedMax(currentState)) {
String categoryId = event.categoryId;
try {
if (currentState is InitialProductsState) {
final posts = await _fetchProducts(categoryId, 0, 3);
yield ProductsLoaded(product: posts, hasReachedMax: false);
return;
}
if (currentState is ProductsLoaded) {
final posts =
await _fetchProducts(categoryId, currentState.product.length, 6);
yield posts.isEmpty
? currentState.copyWith(hasReachedMax: true)
: ProductsLoaded(
product: currentState.product + posts,
hasReachedMax: false,
);
}
} catch (e) {
print(e.toString());
yield ProductsError();
}
}
}
bool _hasReachedMax(ProductsState state) =>
state is ProductsLoaded && state.hasReachedMax;
Future<List<Products>> _fetchProducts(
String categoryId, int startIndex, int limit) async {
final response = await http.Client().post(Configuration.url +
'api/getProductsTest/$categoryId/$startIndex/$limit');
if (response.statusCode == 200) {
List<dynamic> responseData = jsonDecode(response.body);
final List<Products> products = [];
responseData.forEach((singleProduct) {
products.add(Products(
productId: singleProduct['productId'],
productName: singleProduct['productName'],
isNew: singleProduct['isNew'],
isHot: singleProduct['isHot'],
productImage: singleProduct['productImage'],
categoryId: singleProduct['categoryId'],
productPrice: singleProduct['productPrice'],
productDescription: singleProduct['productDescription'],
isLiked: singleProduct['isLiked'],
productColorId: singleProduct['productColorId'],
image1: singleProduct['image1'],
image2: singleProduct['image2'],
image3: singleProduct['image3'],
childCategoryId: singleProduct['childCategoryId']));
});
return products;
} else {
throw Exception('error fetching posts');
}
}
}
It is working fine, now i want to adding filter item, so i create new event
class FilterProductEvent extends ProductsEvent {
String childCategoryId, categoryId;
FilterProductEvent({this.childCategoryId,#required this.categoryId});
#override
List<Object> get props => null;
}
then i create new state
class ProductsFiltered extends ProductsState {
final List<Products> product;
final bool hasReachedMax;
final String filter;
const ProductsFiltered({
this.product,
this.hasReachedMax,
this.filter
});
ProductsFiltered copyWith({
List<Products> product,
bool hasReachedMax,
}) {
return ProductsFiltered(
product: product ?? this.product,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
);
}
#override
List<Object> get props => [product, hasReachedMax];
#override
String toString() =>
'PostLoaded { product: ${product.length}, hasReachedMax: $hasReachedMax }';
}
and inside mapEventToState i adding condition
#override
Stream<ProductsState> mapEventToState(ProductsEvent event) async* {
final currentState = state;
print(event);
print(state);
if (event is FetchProductsEvent && !_hasReachedMax(currentState)) {
String categoryId = event.categoryId;
try {
if (currentState is InitialProductsState) {
final posts = await _fetchProducts(categoryId, 0, 3);
yield ProductsLoaded(product: posts, hasReachedMax: false);
return;
}
if (currentState is ProductsLoaded) {
final posts =
await _fetchProducts(categoryId, currentState.product.length, 6);
yield posts.isEmpty
? currentState.copyWith(hasReachedMax: true)
: ProductsLoaded(
product: currentState.product + posts,
hasReachedMax: false,
);
}
} catch (e) {
print(e.toString());
yield ProductsError();
}
}
else if (event is FilterProductEvent && !_hasReachedMax(currentState)) {
String categoryId = event.categoryId;
String childCategoryId = event.childCategoryId;
try {
if (currentState is InitialProductsState) {
var posts = await _fetchProducts(categoryId, 0, 3);
posts.where((a) => a.childCategoryId == childCategoryId).toList();
yield ProductsFiltered(
product: posts, hasReachedMax: false, filter: childCategoryId);
return;
}
if (currentState is ProductsFiltered) {
var posts =
await _fetchProducts(categoryId, currentState.product.length, 6);
var oldPost = currentState.product;
oldPost.where((a) => a.childCategoryId == childCategoryId).toList();
yield posts.isEmpty
? currentState.copyWith(hasReachedMax: true)
: ProductsFiltered(
product: oldPost + posts,
hasReachedMax: false,
filter: childCategoryId);
}
} catch (e) {
print(e.toString());
yield ProductsError();
}
}
}
Then from my UI, i do this
productBloc.add(FilterProductEvent(
categoryId: widget.categoryId,
childCategoryId:
state.childCategory[index].categoryId));
When i run it , the list of my data is not filtered. how can i fix it ? did i miss something ?
I think problem here is missing set posts again after you filter it.
Try it:
var posts = await _fetchProducts(categoryId, 0, 3);
posts = posts.where((a) => a.childCategoryId == childCategoryId).toList();
You can search with 2 ways
From server side :
You have to design your API to return result according to your search query.
From App :
You can simply do the api call 1st and store it in bloc then for search you can make a method which will return the searched List.
List search(keyword){
return list.where((item)=>item.contains(keyword)).toList();
}