Firestore collection map to list - flutter

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.

Related

Flutter shared_preferences save list<any_class>

I tried to save a list to my shared preferences but the list isn't a String list it is a list of a special type class "todo". I tried it with ".cast<Todo>();", this worked with prefs.getStringList.... but dont work with prefs.setStringList.
Here is a screenshot:
When I try to do prefs.setStringList("shoppingCard", _todos); it says: "The argument type 'List' can't be assigned to the parameter type 'List'."
This is the source code of the class todo:
class Todo {
Todo({required this.name, required this.checked});
final String name;
bool checked;
}
class TodoItem extends StatelessWidget {
TodoItem({
required this.todo,
required this.onTap,
}) : super(key: ObjectKey(todo));
final Todo todo;
final Function onTap;
TextStyle? _getTextStyle(bool checked) {
if (!checked) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
onTap(todo);
},
leading: CircleAvatar(
child: Text(todo.name[0]),
),
title: Text(todo.name, style: _getTextStyle(todo.checked)),
);
}
}
If you need to save list of custom class you need convert it to string. First change your class model to this:
class Todo {
Todo({required this.name, required this.checked});
final String name;
bool checked;
static Todo fromJson(Map<String, dynamic> json) {
return Todo(name: json['name'], checked: json['checked']);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'checked': checked,
};
}
}
then for saving your list in SharedPreferences, try this:
var prefs = await SharedPreferences.getInstance();
prefs.setString('shoppingCard',
jsonEncode({'data': _todos.map((e) => e.toJson()).toList()}));
and for getting it, try this:
var prefs = await SharedPreferences.getInstance();
String? str = prefs.getString('shoppingCard');
if (str != null) {
var result = (jsonDecode(str) as Map)['data'] as List;
result.map((e) => Todo.fromJson(e)).toList();
}
for example in you case, lets say we have list below:
List<Todo> _todos = [
Todo(checked: false, name: 'test1'),
Todo(checked: true, name: 'test2')
];
we add this list to SharedPreferences, like this:
Future<void> _addTodoItem(String name) async {
var prefs = await SharedPreferences.getInstance();
prefs.setString('shoppingCard',
jsonEncode({'data': _todos.map((e) => e.toJson()).toList()}));
_textFieldController.clear();
}
and we get list from SharedPreferences, like this:
Future<void> _getodoItem(String name) async {
var prefs = await SharedPreferences.getInstance();
var value = prefs.getString('shoppingCard');
if (value != null) {
var result = (jsonDecode(value) as Map)['data'] as List;
setState(() {
_todos = result.map((e) => Todo.fromJson(e)).toList();
});
}
}

Retrieve Data from Realtime Database in Flutter

I want to retrieve data from a realtime database for a flutter application. My data is built like this.
I need to loop through this data to display it on the application (ecommerce app, obviously). I have tried and failed in many ways. Currently when trying to get the data I see "Instance of '_Future'" as the message.
class Cart extends StatefulWidget {
Cart({Key? key}) : super(key: key);
#override
State<Cart> createState() => _CartState();
}
class _CartState extends State<Cart> {
DatabaseReference ref = FirebaseDatabase.instance.ref();
Object? products;
List productList = [];
String displayText = 'Results go here!';
snapshot() async {
final snapshot = await ref.child('Products').get();
productList = [];
if (snapshot.exists) {
productList.add(snapshot.value);
products = (snapshot.value);
print(snapshot);
print(snapshot.value);
} else {
print('No Data Available');
}
}
#override
void initState() {
super.initState();
snapshot();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const PreferredSize(
preferredSize: Size.fromHeight(60), child: MyAppBar()),
body: Column(
children: [
ElevatedButton(
onPressed: () async {
// await ref.set({"name": "Tyler"});
snapshot();
},
child: Text("Add Data"),
),
Text("${snapshot()}", style: TextStyle(color: Colors.white))
],
)
);
}
}
I also have this data class built from other posts I have seen. I have to admit, I am not entirely sure how to use it.
import 'dart:convert';
class ProductData {
final int productID;
final String productCategory;
final String productDesc;
final String productName;
final String productPrice;
final String productSize;
final bool productInStock;
final String productImage1;
final String productGender;
final String productImage2;
ProductData(
{required this.productID,
required this.productCategory,
required this.productDesc,
required this.productName,
required this.productPrice,
required this.productSize,
required this.productInStock,
required this.productImage1,
required this.productGender,
required this.productImage2});
ProductData copyWith(
{int? productID,
String? productCategory,
String? productDesc,
String? productName,
String? productPrice,
String? productSize,
bool? productInStock,
String? productImage1,
String? productGender,
String? productImage2}) {
return ProductData(
productID: productID ?? this.productID,
productCategory: productCategory ?? this.productCategory,
productDesc: productDesc ?? this.productDesc,
productName: productName ?? this.productName,
productPrice: productPrice ?? this.productPrice,
productSize: productSize ?? this.productSize,
productInStock: productInStock ?? this.productInStock,
productImage1: productImage1 ?? this.productImage1,
productGender: productGender ?? this.productGender,
productImage2: productImage2 ?? this.productImage2,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'productID': productID,
'productCategory': productCategory,
'productDesc': productDesc,
'productName': productName,
'productPrice': productPrice,
'productSize': productSize,
'productInStock': productInStock,
'productImage1': productImage1,
'productGender': productGender,
'productImage2': productImage2,
};
}
factory ProductData.fromMap(Map<String, dynamic> map) {
return ProductData(
productID: map['productID'] as int,
productCategory: map['productCategory'] as String,
productDesc: map['productDesc'] as String,
productName: map['productName'] as String,
productPrice: map['productPrice'] as String,
productSize: map['productSize'] as String,
productInStock: map['productInStock'] as bool,
productImage1: map['productImage1'] as String,
productGender: map['productGender'] as String,
productImage2: map['productImage2'] as String,
);
}
String toJson() => json.encode(toMap());
factory ProductData.fromJson(String source) =>
ProductData.fromMap(json.decode(source) as Map<String, dynamic>);
#override
String toString() {
return 'ProductData(productID: $productID, productCategory: $productCategory, productDesc: $productDesc, productName: $productName, productPrice: $productPrice, productSize: $productSize, productInStock: $productInStock, productImage11: $productImage1, productGender: $productGender, productImage2: $productImage2)';
}
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ProductData &&
other.productID == productID &&
other.productCategory == productCategory &&
other.productDesc == productDesc &&
other.productName == productName &&
other.productPrice == productPrice &&
other.productSize == productSize &&
other.productInStock == productInStock &&
other.productImage1 == productImage1 &&
other.productGender == productGender &&
other.productImage2 == productImage2;
}
#override
int get hashCode {
return productID.hashCode ^
productCategory.hashCode ^
productDesc.hashCode ^
productName.hashCode ^
productPrice.hashCode ^
productSize.hashCode ^
productInStock.hashCode ^
productImage1.hashCode ^
productGender.hashCode ^
productImage2.hashCode;
}
}
Since the data is loaded from Firebase asynchronously, its get() method returns a Future. That's also why you had to declare your snapshot() function as async, which means that you also return a Future.
On its own the rendering code doesn't know anything about Futures, so it renders it by calling its toString() method, which leads to the output you see:
Instance of '_Future'
What you want instead is to wait for the future to resolve, which is just a fancy way of saying that you want to wait for the data to load. An easy way to do that is to use a FutureBuilder, which handles the asynchronous nature of a Future and all possible states it can be in.
That'd look something like:
snapshot() async {
final snapshot = await ref.child('Products').get();
productList = [];
if (snapshot.exists) {
productList.add(snapshot.value);
products = (snapshot.value);
} else {
print('No Data Available');
}
return productList;
}
body: Column(
children: [
ElevatedButton(
onPressed: () async {
snapshot();
},
child: Text("Add Data"),
),
FutureBuilder(
future: snapshot(),
builder: (BuildContext context, AsyncSnapshot asyncSnapshot) {
if (snapshot.hasData) {
var productList = asyncSnapshot.data! as List;
return Text(productList.length.toString());
} else if (snapshot.hasError) {
return Text('Error: ${asyncSnapshot.error}');
} else {
return CircularProgressIndicator(),
}
}
)
],
)

Understanding and implementing OOP (in flutter/dart) to abstract shared structure and functionality

(Edited to clarify & update with progress)
When building an app with various lists of items and some cross-referencing between them; e.g. I have a stock class and a StockSet that extends ChangeNotifier as well as holds some basic rest functionality etc. But I'm also going to have Clients, Meetings etc, all of which are basically the same except for minor differences in fields.
So I recon I should define a parent class, say Item and ItemSet that, and then the actual elements in my app will extend those. - Or is that over-engineering?
However, in say my current StockSet I have methods to fetch either a single, or a number of stock items, i.e. ...Future<Stock> _fetchAStock() async {...
the only significant difference between it and say client records will be the REST url, and type of object returned (and give or take a few fields).
Is it better to have individual methods in all the child level classes - seems simpler though more code = more potential errors etc. OR build the fetch functionality into the parent class with some kind of abstraction/specification/parameterization(/or external configuration look-up) of the case specific differences?
New-ish to OOP and Flutter/Dart and it seems clearer some days than others... this is an others :-)
What I have:
class StocksSetRoute extends StatelessWidget with asc_alertBar {
const StocksSetRoute({Key? key}) : super(key: key);
static const indexStr = 'stocks';
static const labelStr = 'Properties';
static GlobalKey myKey = GlobalKey();
static GlobalKey parentKey = GlobalKey();
#override
Widget build(BuildContext context) {
var stockSet = context.watch<StockSet>();
return Scaffold(
key: parentKey,
appBar: AppBar(
title: const TitleRow(indexStr: indexStr, labelStr: labelStr),
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// these will be filters, order toggle etc.
children: [
ElevatedButton(
onPressed: (stockSet.isRemoteEmpty)
? null
: () async {
try {
await stockSet.fetch(5);
} catch (e) {
alertBar(
'Could not fetch any more. ', stockSet as ItemSet,
backgroundColor: Colors.grey, context: context);
}
},
child: const Text('Fetch More'),
),
ElevatedButton(
onPressed:
(stockSet.stocks.isEmpty) ? null : () => stockSet.clear(),
child: const Text('Clear'),
),
],
),
Expanded(
child: StockListViewBuilder(
stockSet: stockSet, theKey: myKey, alert: alertBar))
],
),
);
}
}
class StockListViewBuilder extends StatefulWidget {
final StockSet stockSet;
final GlobalKey theKey;
final Function alert;
const StockListViewBuilder({
Key? key,
required this.stockSet,
required this.theKey,
required this.alert,
}) : super(key: key);
#override
State<StockListViewBuilder> createState() => _StockListViewBuilderState();
}
class _StockListViewBuilderState extends State<StockListViewBuilder>
with asc_alertBar {
final ScrollController _scrollController = ScrollController();
late double _scrollPosition;
late double _maxScrollExtent;
late bool isThisTheEnd = false;
_scrollListener() async {
setState(() {
_scrollPosition = _scrollController.position.pixels;
_maxScrollExtent = _scrollController.position.maxScrollExtent;
});
if (!isThisTheEnd && _scrollPosition / _maxScrollExtent > 0.90) {
isThisTheEnd = true;
if (widget.stockSet.isRemoteEmpty) {
alertBar('No more items available', null, context: context);
} else {
await widget.stockSet.fetch(5);
}
}
if (isThisTheEnd && _scrollPosition / _maxScrollExtent <= 0.90) {
isThisTheEnd = false;
}
}
#override
void initState() {
super.initState();
int listCount;
_scrollController.addListener(_scrollListener);
WidgetsBinding.instance.addPostFrameCallback((_) async {
listCount = widget.stockSet.stocks.length;
if (listCount < 10 && !widget.stockSet.isRemoteEmpty) {
try {
await widget.stockSet.fetch(10);
} catch (e) {
super.setState(() {
widget.alert("Can't load stock.", widget.stockSet,
backgroundColor: Colors.red);
});
}
}
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.vertical,
controller: _scrollController,
shrinkWrap: true,
key: widget.theKey,
itemCount: widget.stockSet.stocks.length + 1,
itemBuilder: (context, index) {
if (index <= widget.stockSet.stocks.length - 1) {
return InkWell(
onTap: (() => Navigator.pushNamed(
context,
'/stocks/stock',
arguments: ScreenArguments(widget.stockSet.stocks[index]),
)),
child: StockListItem(
stock: widget.stockSet.stocks[index],
));
} else {
return LoadingItemNotifier(
isLoading: widget.stockSet.isBusyLoading,
);
}
},
);
}
}
class StockSet extends ItemSet {
final List<Stock> _stocks = [];
List<Stock> get stocks => _stocks;
List<int> getHaveStocksIds() {
final List<int> ids = _stocks.map((stock) => stock.id).toList();
return ids;
}
void add(Stock stock) {
_stocks.add(stock);
notifyListeners();
}
void remove(Stock stock) {
_stocks.remove(stock);
notifyListeners();
}
void clear() {
_stocks.clear();
isBusyLoading = false;
isRemoteEmpty = false;
notifyListeners();
}
Future<void> fetch([int num = 1]) async {
int i = 0;
for (i; i < num; i++) {
if (!isRemoteEmpty) {
try {
Stock tmpStock = await _fetchAStock();
if (getHaveStocksIds().contains(tmpStock.id)) {
throw Exception('We allready have ${tmpStock.id}');
}
add(tmpStock);
} catch (e) {
i = num;
isRemoteEmpty = true;
isBusyLoading = false;
notifyListeners();
throw Exception('No more to fetch $e');
}
}
}
return;
}
Future<Stock> _fetchAStock() async {
List<int> have = getHaveStocksIds();
final queryParameters = {
'exclude': json.encode(have),
};
isBusyLoading = true;
notifyListeners();
try {
final response = await http.post(
Uri.https('onethread.design', 'agency/wp-json/ypagp/v1/pr-get-a',
queryParameters),
);
isBusyLoading = false;
notifyListeners();
if (response.statusCode != 200) {
throw HttpException('${response.statusCode}');
}
final Map<String, dynamic> map = json.decode(response.body);
return Stock(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
thumbUrl: map['thumbUrl'] as String,
);
} on SocketException {
feedback = 'Please enable an internet connection.';
notifyListeners();
} on HttpException {
feedback = "Couldn't find the/a post.";
notifyListeners();
} on FormatException {
feedback = "Bad response format.";
} catch (e, s) {
feedback = 'fetchA catch $e $s ';
}
throw Exception('Could not _fetchAStock');
}
}
class ItemSet extends ChangeNotifier {
bool isBusyLoading = false;
bool isRemoteEmpty = false;
String? feedback;
}
what I'm aiming at (and have partially succeeded in implementing) is something like
class StockSet extends ItemSet and class ItemSet extends ChangeNotifier {...
class Item extends Equatable {
const Item({
required this.id,
required this.title,
required this.description,
});
final int id;
final String title;
final String description;
#override
List<Object> get props => [id, title, description];
}
and I think then
class Stock extends Item {
final String thumbUrl;
const Stock(
{required super.id, required super.title, required super.description, required this.thumbUrl});
#override
List<Object> get props => [id, title, description, thumbUrl];
}
though when it comes to the ItemSet's methods, e.g.
been trying things like e.g.
in ItemSet
Future<Type> fetchAItem(String typeSlug, Type type) async {
List<int> have = getHaveItemsIds();
final queryParameters = {
'exclude': json.encode(have),
};
developer.log('_fetchAItem $queryParameters');
isBusyLoading = true;
notifyListeners();
try {
final response = await http.post(
Uri.https('###', 'a###/v1/$typeSlug-get-a',
queryParameters),
);
developer.log('response.statusCode:${response.statusCode}');
isBusyLoading = false;
notifyListeners();
if (response.statusCode != 200) {
throw HttpException('${response.statusCode}');
}
final Map<String, dynamic> map = json.decode(response.body);
return Item(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
) as type;// no! - was worth a guess though
} on...
but I'm getting lost in the plumbing, should I use #override in the child classes? seems clunky - or maybe use more mixins?.
Thanks in advance
and the alertBar mixin...
mixin asc_alertBar {
void alertBar(
String message,
ItemSet? itemSet, {
String label = 'Okay',
Function? action,
Color? backgroundColor,
required BuildContext context,
}) {
if (itemSet != null) {
String? itemSetFeedback = itemSet.feedback;
if (itemSetFeedback != null) {
message += '\n$itemSetFeedback';
itemSet.feedback = null;
}
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: backgroundColor,
duration: const Duration(seconds: 15),
content: AnimatedText(
textContent: message,
durationFactor: 0.25,
),
action: SnackBarAction(
label: label,
onPressed: () => action,
),
),
);
}
}
I hope the question makes sense.

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

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)));

Can't access values from redux state event hough I have an instance of the class

I have some state that is added during some middleware. This state is used to build ListTiles for a ListView. I cannot access the properties of this instance when I map over the instance.
I can see the info in the debugger: https://imgur.com/a/YTpjBou
But I cannot access the property because it returns null. I am unsure if this is because the future has not completed by the time it renders or what.
Here is the build for the home_widget
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:nasp_portal_app/model/model.dart';
import 'main_drawer.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Image.asset('lib/images/Logo.png', height: 35),
),
drawer: DrawerOnly(),
body: StoreConnector<AppState, _ViewModel>(
converter: (Store<AppState> store) => _ViewModel.create(store),
builder: (BuildContext context, _ViewModel viewModel) => Column(
children: <Widget>[Expanded(child: ItemListWidget(viewModel))],
),
),
);
}
}
class ItemListWidget extends StatelessWidget {
final _ViewModel model;
ItemListWidget(this.model);
#override
Widget build(BuildContext context) {
return ListView(
children: model.tournaments.map((Tournament tournament) {
return ListTile(
title: Text(tournament.tournName ?? 'Test'),
leading: IconButton(
icon: Icon(Icons.home),
onPressed: () => print('go to tourney'),
));
}).toList(),
);
}
}
class _ViewModel {
final List<Tournament> tournaments;
_ViewModel({this.tournaments});
factory _ViewModel.create(Store<AppState> store) {
print(store.state.tournaments.length);
return _ViewModel(tournaments: store.state.tournaments);
}
}
Here is the class definition of a Tournament
class Tournament {
final String tournName;
final String tournState;
final String tournCity;
final double distanceMiles;
final int startDate;
final int endDate;
final int tID;
Tournament({
#required this.tournName,
#required this.tournState,
#required this.tournCity,
#required this.distanceMiles,
#required this.startDate,
#required this.endDate,
#required this.tID,
});
Tournament copyWith({
String tournName,
String tournState,
String tournCity,
double distanceMiles,
int startDate,
int endDate,
int tID,
}) {
return Tournament(
tournName: tournName ?? this.tournName,
tournState: tournState ?? this.tournState,
tournCity: tournCity ?? this.tournCity,
distanceMiles: distanceMiles ?? this.distanceMiles,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
tID: tID ?? this.tID,
);
}
}
This is my redux middleware handling the async task
class NearTournamentsMiddleware extends MiddlewareClass<AppState> {
#override
void call(Store<AppState> store, dynamic action, NextDispatcher next) {
if (action is NearTournamentsAction) {
checkNearTournaments(next);
}
next(action);
}
void checkNearTournaments(NextDispatcher next) async {
final tournaments = await _tournamentsInRange();
for (final tournament in tournaments) {
next(AddTournamentsAction(
tournament['TournName'],
tournament['TID'],
tournament['TournState'],
tournament['TournCity'],
tournament['Distance_Miles'],
tournament['Start_Date'],
tournament['End_Date']));
}
}
_tournamentsInRange() async {
Map currentLocation = <String, double>{};
var location = Location();
try {
currentLocation = await location.getLocation();
final response = await _checkLocalTournaments(
currentLocation["latitude"], currentLocation["longitude"]);
final decoded = jsonDecode(response.body);
return decoded;
} on PlatformException {
currentLocation = null;
}
}
Future<http.Response> _checkLocalTournaments(lat, lng) async {
var url = 'https://napi.com';
var body = json.encode({
'miles': '-1', // -1 for test api
'lat': lat,
'lng': lng
});
Map<String, String> headers = {
'Content-type': 'application/json',
'Accept': 'application/json',
};
final response = await http.post(url, body: body, headers: headers);
return response;
}
}
These are my current reducers
import 'package:nasp_portal_app/model/model.dart';
import 'package:nasp_portal_app/redux/actions.dart';
AppState appStateReducer(AppState state, action) {
return AppState(tournaments: tournamentReducer(state.tournaments, action));
}
List<Tournament> tournamentReducer(List<Tournament> state, action) {
if (action is AddTournamentsAction) {
return []
..addAll(state)
..add(Tournament(
tournName: action.tournName,
tournState: action.tournState,
tournCity: action.tournCity,
distanceMiles: action.distanceMiles,
startDate: action.startDate,
endDate: action.endDate,
tID: action.tID));
}
return state;
}
How can I properly access the values in the map in my screenshot? I know I have an instanced based on the debugger but cannot get its properties.
My issue was with the redux action that I was using called AddTournamentsAction
I was not using this to refer to the class variables in its constructor like so:
class AddTournamentsAction {
final String tournName;
final String tournState;
final String tournCity;
final double distanceMiles;
final int startDate;
final int endDate;
final int tID;
AddTournamentsAction(
tournName,
tournState,
tournCity,
distanceMiles,
startDate,
endDate,
tID,
);
}
To fix this I simply had to add the this keyword:
class AddTournamentsAction {
final String tournName;
final String tournState;
final String tournCity;
final double distanceMiles;
final int startDate;
final int endDate;
final int tID;
AddTournamentsAction(
this.tournName,
this.tournState,
this.tournCity,
this.distanceMiles,
this.startDate,
this.endDate,
this.tID,
);
}