Closed sahilmob closed 5 years ago
AppState
needs to be json encodable for the state to be visible in devtools, which means it needs a toJson
method. Can you share the class definition for AppState?
Almost working,
import './restaurant.dart';
import './category.dart';
import './product.dart';
import './cart_item.dart';
import 'package:flutter/foundation.dart';
class AppState {
final Restaurant restaurant;
final List<ProductsCategory> categories;
final Map<int, List<Product>> products;
final Map<int, CartItem> cart;
final bool loading;
final String error;
final String locale;
final double subTotal;
AppState(
{@required this.restaurant,
this.locale,
this.categories,
this.products,
this.cart,
this.subTotal,
this.loading,
this.error});
AppState copyWith(
{Restaurant restaurant,
String locale,
List<ProductsCategory> categories,
Map<int, List<Product>> products,
Map<int, CartItem> cart,
double subTotal,
bool loading,
String error}) {
return AppState(
restaurant: restaurant ?? this.restaurant,
locale: locale ?? this.locale,
categories: categories ?? this.categories,
products: products ?? this.products,
cart: cart ?? this.cart,
subTotal: subTotal ?? this.subTotal,
loading: loading ?? this.loading,
error: error ?? this.error);
}
Map toJson() {
return {
"restaurant": restaurant,
"locale": locale,
"categories": categories,
"products": products,
"cart": cart,
"subTotal": subTotal,
"loading": loading,
"error": error
};
}
@override
String toString() {
return toJson().toString();
}
AppState.initialState()
: restaurant = null,
categories = null,
products = {},
cart = {},
subTotal = 0,
loading = false,
error = null,
locale = null;
}
I can see the state until I start storing products which has the following class definition:
import "package:flutter/foundation.dart";
class Product {
final int id;
final String nameEn;
final String nameAr;
final String imageUrl;
final String calories;
final String caloriesUnit;
final double price;
Product(
{@required this.id,
@required this.nameEn,
@required this.nameAr,
this.calories,
this.caloriesUnit,
this.price,
this.imageUrl});
Product copyWith(
{int id,
String nameEn,
String nameAr,
String imageUrl,
String calories,
String caloriesUnit,
double price}) {
return Product(
id: id ?? this.id,
nameEn: nameEn ?? this.nameEn,
nameAr: nameAr ?? this.nameAr,
imageUrl: imageUrl ?? this.imageUrl,
calories: calories ?? this.calories,
caloriesUnit: caloriesUnit ?? this.caloriesUnit,
price: price ?? this.price);
}
factory Product.fromJson(Map productData) {
String calories;
String caloriesUnit;
(productData['meta_data'] as List).forEach((m) {
if (m['key'] == 'wccaf_cal') {
calories = m['value'];
}
});
if (calories != null && calories.isNotEmpty) {
(productData['meta_data'] as List).forEach((m) {
if (m['key'] == 'wccaf_per') {
caloriesUnit = m['value'];
}
});
}
return Product(
id: productData['id'],
nameAr: productData['name'],
nameEn:
productData['short_description'].replaceAll(RegExp("<[^>]*>"), ""),
calories: calories,
caloriesUnit: caloriesUnit,
price: double.parse(productData['price']),
imageUrl:
productData['images'] != null && productData['images'][0] != null
? productData['images'][0]['src']
: null);
}
Map toJson() {
return {
"id": id,
"nameEn": nameEn,
"nameAr": nameAr,
"imageUrl": imageUrl,
"calories": calories,
"caloriesUnit": caloriesUnit,
"price": price
};
}
@override
String toString() {
return toJson().toString();
}
}
After instantiating the product class, state becomes undefined.
You will need to test whether jsonEncode(product)
works as expected. One thing I have noticed is Dart is fairly fussy with types - you may need to explicitly define them:
Map<int, dynamic> toJson() {
...
}
Unfortunately jsonEncode
doesn't give you much information when the encoding fails, so you'll need to add unit tests to make sure each component of your state can be encoded.
Somehow the products map in the app state is causing issue
I tried to covert the products in the app state (in toJson method) to string
Map<String, dynamic> toJson() {
return {
"restaurant": restaurant,
"locale": locale,
"categories": categories,
"products": products.toString(),
"cart": cart,
"subTotal": subTotal,
"loading": loading,
"error": error
};
}
after that, I cant see the state, however, the products list is treated as a string.
I highly recommend adding unit tests for your state classes. For example:
import 'dart:convert';
import 'package:json_test/app_state.dart';
import 'package:json_test/product.dart';
import 'package:json_test/restaurant.dart';
import 'package:test/test.dart';
void main() {
group(AppState, () {
test('initial state', () {
Restaurant restaurant = Restaurant(name: 'Tuno');
AppState state = AppState.initialState();
// encode the object to a string, then decode back into a map
final String encoded = jsonEncode(state);
final Map<String, dynamic> result = jsonDecode(encoded);
expect(result, isNotNull);
expect(result, TypeMatcher<Map<String, dynamic>>());
});
test('works with some products', () {
Restaurant restaurant = Restaurant(name: 'Tuno');
Map<int, List<Product>> products = {
12: [Product(id: 1234), Product(id: 4321)]
};
AppState state = AppState.initialState().copyWith(products: products);
// encode the object to a string, then decode back into a map
final String encoded = jsonEncode(state);
final Map<String, dynamic> result = jsonDecode(encoded);
expect(result, isNotNull);
expect(result, TypeMatcher<Map<String, dynamic>>());
});
});
}
If you run this, you will see that the Map<int, Product>
cannot be encoded.
Converting object to an encodable object failed: Instance of 'AppState'
dart:convert jsonEncode
test/app_state_test.dart 28:30 main.<fn>.<fn>
However, this is intended behaviour. jsonEncode
can only convert maps that use a String as the key (Map<String, Product>
);
If you change your AppState
to use Strings for keys, then the encoding works. Unfortunately we don't get very nice error messages from the jsonEncoder, which is why it is so important to unit test the building blocks of your state.
Related issues:
https://github.com/dart-lang/sdk/issues/23150 https://github.com/dart-lang/sdk/issues/32476
I can see the dispatched action, but state is always undignified.
I connect to the store in the main fuction:
and my reducer looks like
and the reducer is divided into several TypedReducers e.g:
and I'm using
and fluttrer