I have a main screen with several Buttons which push the user to different screens and I have a FAB which pushs the user to a screen he saved as favorite. I do this with a cubit from BLoC: (FAB onPress function): Navigator.push(context, MaterialPageRoute(builder: (context) => state.favWidget!));
Problem: I try to save the state of the Cubit (Shared prefs or Hydrated, dosent matter for me - I prefere HydratedCubit) so that the user reaches his favorite screen even after a restart.
Here is the FavWidgetState:
class FavWidgetState extends Equatable {
factory FavWidgetState.initial() {
return const FavWidgetState(favWidget: null);
}
final Widget? favWidget;
const FavWidgetState({
required this.favWidget,
});
Map<String, dynamic> toJson() {
return {
'favWidget': this.favWidget,
};
}
factory FavWidgetState.fromJson(Map<String, dynamic> json) {
return FavWidgetState(
favWidget: json['favWidget'] as Widget,
);
}
#override
List<Object?> get props => [favWidget];
I added the fromJson and toJson with the dart dataclass extention
Inside FaveWidgetcubit is just little code:
FavWidgetCubit() : super(FavWidgetState.initial());
void setFaveWidget({required Widget? widget})async{
emit(FavWidgetState(favWidget: widget));
}
#override
FavWidgetState? fromJson(Map<String, dynamic> json) {
return FavWidgetState.fromJson(json);
}
#override
Map<String, dynamic>? toJson(FavWidgetState state) {
return state.toJson();
}
You can imagine that this won't with the error: Unhandled Exception: Converting object to an encodable object failed: Instance of ...
How do I make the Widget? widget to a convertible object?
Related
I have my model for json, service to get api
I just dont know how to get image like this like this
from this json from this
the Ipone Mega is the carousel slider(4 images in json link), below the other is just column
if you could show it in carousel Slider i will be very grateful to you
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_work/apiService/fetch_data.dart';
import 'package:my_work/apiService/phone.dart';
class CarouselSliderData extends StatefulWidget{
const CarouselSliderData({super.key});
#override
State<CarouselSliderData> createState() => CarouselSliderDataState();
}
class CarouselSliderDataState extends State<CarouselSliderData> {
Phone? info;
#override
void initState() {
DioService.getDataMocky(
url:'https://run.mocky.io/v3/654bd15e-b121-49ba-a588-960956b15175'
).then((value) async {
if(value != null){
setState((){
info = value!;
});
}
}).catchError(
(value) => (value),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image(image:)
],
);
}
}
Step 1: get the json from API. I call this variable is
Map<String, dynamic> json;
Step 2: As you can see, "home_store" of your json is a list of phones. Therefore, you create a model class called PhoneModel like this:
class PhoneModel {
int id;
bool isNew;
String title;
String subtitle;
String picture;
bool isBuy;
/// constructor
PhoneModel({this.id ...});
/// from json to class model
PhoneModel.fromJson(Map<String, dynamic> json) {
this.id = json["id"];
this.isNew = json["is_new"];
...
}
}
Then do like this to catch the list of items:
List<PhoneModel> listPhones = List<PhoneModel>.from(
(json['home_store'] as List).map((e) => PhoneModel.fromJson(e)),
);
Now your list of phone is variable listPhones. Url is field picture. Do Image.network or anythings else... Good luck!
I want to encode my Map to a json. It looks like this:
Map<MyEnum, int> map = {type: limit};
Where MyEnum is an enum. A simple json.encode(map) won't work as it does not know how to serialize the enum class I guess.
The error message is:
Unhandled Exception: Converting object to an encodable object failed: _LinkedHashMap len:1
How can I manage to serialize this map to a json?
you can use describeEnum method inside foundation.dart
This is really not a solution I would recommend but I ended up doing it mostly for "fun". I don't guarantee anything about the solution besides the fact that it is horrible. :)
The problem is that enum is not defined as a valid type for Json so the whole concept does give us some problems. One solution is to translate enum values into String with the name of the enum first, and then the name of value like MyFirstEnum.first1. This representation is what Dart gives you if calling toString() on a enum value.
This is fine but for safety we could also add a magic string in the beginning like DART_ENUM:MyFirstEnum.first1 so it is easier to recognize between other strings which could have the same name as the enum value without being an enum.
Next is type safety. In Json, we know that all maps has String as the type of keys. By making our own representation of enum and allowing it to also be keys, we cannot expect a decoder to return e.g. Map<String, dynamic> but must return Map<dynamic, dynamic>.
With that said, here is my attempt to build a Json decoder and encoder which handles enum values. It also works for enum keys in maps:
import 'dart:convert';
class JsonConverterWithEnumSupport {
final String magicString;
final Set<Object> allEnumValues = {};
final Map<String, Object> enumStringToEnumValue = {};
JsonConverterWithEnumSupport(List<List<Object>>? enumsValues,
{this.magicString = "DART_ENUM:"}) {
enumsValues?.forEach(addEnumValues);
}
void addEnumValues(List<Object> enumValues) {
for (final enumValue in enumValues) {
enumStringToEnumValue[enumValue.toString()] = enumValue;
allEnumValues.add(enumValue);
}
}
String _addMagic(dynamic enumValue) => '$magicString$enumValue';
String _removeMagic(String string) => string.substring(magicString.length);
String encode(Object? value) =>
json.encode(value, toEncodable: (dynamic object) {
if (object is Map) {
return object.map<dynamic, dynamic>((dynamic key, dynamic value) =>
MapEntry<dynamic, dynamic>(
allEnumValues.contains(key) ? _addMagic(key) : key,
allEnumValues.contains(value) ? _addMagic(value) : value));
}
if (object is List) {
return object.map<dynamic>(
(dynamic e) => allEnumValues.contains(e) ? _addMagic(e) : e);
}
if (allEnumValues.contains(object)) {
return _addMagic(object);
}
return object;
});
dynamic decode(String source) => json.decode(source, reviver: (key, value) {
if (value is String && value.startsWith(magicString)) {
return enumStringToEnumValue[_removeMagic(value)];
}
if (value is Map) {
return value.map<dynamic, dynamic>((dynamic key, dynamic value) =>
MapEntry<dynamic, dynamic>(
(key is String) && key.startsWith(magicString)
? enumStringToEnumValue[_removeMagic(key)]
: key,
value));
}
return value;
});
}
enum MyFirstEnum { first1, first2 }
enum MySecondEnum { second1, second2 }
void main() {
final converter =
JsonConverterWithEnumSupport([MyFirstEnum.values, MySecondEnum.values]);
final jsonString = converter.encode({
MyFirstEnum.first1: [MySecondEnum.second2, MySecondEnum.second1],
'test': {MyFirstEnum.first2: 5}
});
print(jsonString);
// {"DART_ENUM:MyFirstEnum.first1":["DART_ENUM:MySecondEnum.second2","DART_ENUM:MySecondEnum.second1"],"test":{"DART_ENUM:MyFirstEnum.first2":5}}
print(converter.decode(jsonString));
// {MyFirstEnum.first1: [MySecondEnum.second2, MySecondEnum.second1], test: {MyFirstEnum.first2: 5}}
}
You will need to feed into JsonConverterWithEnumSupport all the possible enum values there is possible (see the main method in the bottom for example).
If you don't want the magic string to be appended on each enum you can just create JsonConverterWithEnumSupport with: magicString: '' as parameter.
You could create an extension on your enum to convert it to a String then convert your map to a Map<String, int> so it will be encoded correctly:
import 'dart:convert';
enum MyEnum { type }
extension MyEnumModifier on MyEnum {
String get string => this.toString().split('.').last;
}
void main() {
Map<MyEnum, int> map = {MyEnum.type: 10};
Map<String, int> newMap = {};
map.forEach((key, value) =>
newMap[key.string] = value);
final json = jsonEncode(newMap);
print(json);
}
Output
{"type":10}
you will need a function to convert string to enum:
T enumFromString<T>(List<T> values, String value) {
return values.firstWhere((v) => v.toString().split('.')[1] == value, orElse: () => null);
}
your enum is
enum MyEnum {type, destype};
suppose it is used as map inside a class to serialize and deserialize:
class MyClass {
Map<MyEnum, int> myProperty = {type: 1};
// serialize
Map<String, dynamic> toJson() {
return {
'myProperty': myProperty.map((key, value) => MapEntry(key.name, value)),
}
// deserialize
MyClass.fromJson(Map<String, dynamic> json) {
myProperty=
json['myProperty'].map((k, v) => MapEntry(enumFromString<MyEnum>(MyEnum.values, k), v)).cast<MyEnum, int>();
}
}
Firstly define enum and value extension
enum OrderState { pending, filled, triggered, cancelled }
extension OrderStateExt on OrderState {
String get value {
switch (this) {
case OrderState.pending:
return "PENDING";
case OrderState.filled:
return "FILLED";
case OrderState.triggered:
return "TRIGGERED";
case OrderState.cancelled:
return "CANCELLED";
}
}
}
model class
class OrderRequest {
OrderState state;
OrderRequest({required this.state});
Map<String, dynamic> toMap() {
return {
'state': state.value,
};
}
}
String toJson() => json.encode(toMap());
factory OrderRequest.fromMap(Map<String, dynamic> map) {
return OrderRequest (
state: OrderState.values
.where((e) => e.value == map['state']).first
);
}
factory OrderRequest.fromJson(String source) =>
OrderRequest.fromMap(json.decode(source));
I am attempting to build my own implementation of a system for localizing strings for internationalization in Flutter apps, using Flutter's LocalizationsDelegate, and loading the localized strings from JSON files (one json file for each locale).
Everything works fine, but when the app is launched, there's a lapse of some milliseconds in which the screen goes black. The reason for this is that, since I am loading the JSON files using json.decode, the way I am retrieving the localized strings is asynchronous. The app loads its MaterialApp widget and then starts parsing the JSONs for localization. That is when the app goes black until it parses the JSON successfully.
Here is my implementation of my i18n system:
class Localization extends LocaleCodeAware with LocalizationsProvider {
Localization(this.locale) : super(locale.toString());
final Locale locale;
static Localization of(BuildContext context) =>
Localizations.of<Localization>(context, Localization);
}
class AppLocalizationsDelegate extends LocalizationsDelegate<Localization> {
const AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
#override
Future<Localization> load(Locale locale) async {
final localization = Localization(locale);
await localization.load();
return localization;
}
#override
bool shouldReload(AppLocalizationsDelegate old) => false;
}
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:example/resources/asset_paths.dart' as paths;
abstract class LocaleCodeAware {
LocaleCodeAware(this.localeCode);
final String localeCode;
}
mixin LocalizationsProvider on LocaleCodeAware {
static Map<String, String> _values;
Future<void> load() async {
final string = await rootBundle.loadString('${paths.i18n}$localeCode.json');
final Map<String, dynamic> jsonMap = json.decode(string);
_values = jsonMap.map((key, value) => MapEntry(key, value.toString()));
}
String get appTitle => _values['appTitle'];
}
Here is my main.dart file, with its MaterialApp widget.
import 'package:flutter/material.dart';
void main() => runApp(ExampleApp());
class ExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
localizationsDelegates: [
const AppLocalizationsDelegate(),
],
supportedLocales: const [Locale("en"), Locale("es")],
home: const AppNavigator(),
);
}
If instead of having the localized strings in JSON files, I assign a Map<String, String> to my _values map, and I load the strings directly from there, the black screen issue is gone, because the values are hardcoded and can be loaded synchronously.
So my question is, how can I have my app wait in splash screen until the localized strings are loaded from the JSON files?
Do you have any errors in your logs? The black screen could only be caused by 1. The current route not building a visible page or 2. The build() function of the current route throwing exceptions.
As for loading the localizations while on the splash screen, you can do that within your main() function:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
List<Locale> locales = WidgetsBinding.instance.window.locales;
// ... logic to decide which locale to use and load localizations for
final string = await rootBundle.loadString('${paths.i18n}$localeCode.json');
final Map<String, dynamic> jsonMap = json.decode(string);
runApp(ExampleApp(jsonMap));
}
This way, you can read the JSON file and convert it to a Map while on the splash screen, and then pass it to ExampleApp, which can in turn pass it to AppLocalizationsDelegate, which can store it as a local variable and use it within load().
checkout easy_localization package , its simpler than the most out there
I'm working with some complex json in dart, and I have an issue creating objects before I know what type they'll be.
I appreciate the suggestions, but I don't think I completely understand. In the given answer:
var entity = Model();
castToEntity(entity, {'test': 10});
Don't I need to know that it will be a Model class?
What if I have the below two classes:
#JsonSerializable(explicitToJson: true, includeIfNull: false)
class Location {
String id;
String resourceType;
Location({#required this.id, this.resourceType})
factory Location.fromJson(Map<String, dynamic> json) => _$LocationFromJson(json);
Map<String, dynamic> toJson() => _$LocationToJson(this);
}
class Reference {
String reference;
String resourceType;
Location({#required this.reference, this.resourceType}
factory Reference.fromJson(Map<String, dynamic> json) => _$ReferenceFromJson(json);
Map<String, dynamic> toJson() => _$ReferenceToJson(this);
}
And then I query the server, and I don't know what kind of class it will be. It could be a Location, or a Reference, or if it's a list, it could be multiple of both, and I don't know until I've requested it.
var myBundle = Bundle.fromJson(json.decode(response.body));
Each "myBundle.entry" is another resource. I'd like to be able to use information from that resource to define itself. So I could do something like:
myBundle.entry.resourceType newResource = new myBundle.entry.resourceType();
What I'm doing right now is sending it to a function that has all of the possible options predefined:
var newResource = ResourceTypes(myBundle.entry[i].resource.resourceType,
myBundle.entry[i].resource.toJson());
dynamic ResourceTypes(String resourceType, Map<String, dynamic> json) {
if (resourceType == 'Location') return (new Location.fromJson(json));
if (resourceType == 'Reference') return (new Reference.fromJson(json));
}
It was said that there's not reflection in dart, so I didn't know any other way to do it.
As far as I know, it's not possible since Dart doesn't have Reflection like c#, the most close that I can imagine, is using an abstract class that enforces your entity to implement fromJson, and, in that method, you read the Map and put values into fields, like the code below:
abstract class Serializable {
void fromJson(Map<String,dynamic> data);
}
class Model implements Serializable {
int test;
#override
void fromJson(data) {
test = data['test'];
}
}
Serializable castToEntity(Serializable entity, Map<String, dynamic> data) {
return entity..fromJson(data);
}
Now, when you read you database and have the Map, you can call a method generic like:
var entity = Model();
castToEntity(entity, {'test': 10});
print(entity.test);
Where entity is an empty model.
Note: Your fields on entity, cannot be final, since fromJson is an instance method and not a factory method.
I'm wondering how can I parse a nested json to a class with generic types. My intention is to wrap responses from the backend (like loginRespose that contains a token) with a code and a message
I have
class BaseResponse<T>{
int code;
String message;
T responseObject;
BaseResponse.fromJson(Map<String, dynamic> parsedJson)
: code = parsedJson['Code'],
message = parsedJson['Message'],
responseObject = T.fromJson(parsedJson['ResponseObject']); //This is what I'd like to do
}
Obviously the last line throws an error because T doesn't has a named constructor "fromJson".
I tried adding some restrictions to the Type but I didn't find any solutions. Do you have any idea on how to pull this off?
You can't do such thing, at least not in flutter. As dart:mirror is disabled and there's no interface for classes constructors.
You'll have to take a different route.
I'll recommend using POO instead. You would here give up on deserializing responseObject from your BaseResponse. And then have subclass of BaseResponse handles this deserialization
Typically you'd have one subclass per type:
class IntResponse extends BaseResponse<int> {
IntResponse.fromJson(Map<String, dynamic> json) : super._fromJson(json) {
this.responseObject = int.parse(json["Hello"]);
}
}
You can then hide this mess away by adding a custom factory constructor on BaseResponse to make it more convenient to use.
class BaseResponse<T> {
int code;
String message;
T responseObject;
BaseResponse._fromJson(Map<String, dynamic> parsedJson)
: code = parsedJson['Code'],
message = parsedJson['Message'];
factory BaseResponse.fromJson(Map<String, dynamic> json) {
if (T == int) {
return IntResponse.fromJson(json) as BaseResponse<T>;
}
throw UnimplementedError();
}
}
Then either instantiate the wanted type directly, or use the factory constructor :
final BaseResponse foo = BaseResponse.fromJson<int>({"Hello": "42", "Code": 42, "Message": "World"});
You can achieve this with the built_value package (you'll also need built_value_generator and build_runner). Your class will look something like this:
part 'base_response.g.dart';
abstract class BaseResponse<T> implements Built<BaseResponse<T>, BaseResponseBuilder<T>> {
int code;
String message;
T responseObject;
factory BaseResponse([updates(BaseResponseBuilder<T> b)]) = _$BaseResponse<T>;
static Serializer<BaseResponse> get serializer => _$baseResponseSerializer;
}
You will have to run flutter packages pub run build_runner build to make the generated file. Then you use it like this:
BaseResponse baseResponse = serializers.deserialize(
json.decode(response.body),
specifiedType: const FullType(BaseResponse, const [FullType(ConcreteTypeGoesHere)])
);
There's just one more bit of boilerplate you have to take care of. You need another file called serializers.dart. You need to manually add all the classes you want to deserialize here, and also an addBuilderFactory function for each class that takes a type parameter - and for each concrete type you want to use.
part 'serializers.g.dart';
#SerializersFor(const [
BaseResponse,
ConcreteTypeGoesHere,
])
final Serializers serializers = (_$serializers.toBuilder()
..addBuilderFactory(
FullType(BaseResponse, const [const FullType(ConcreteTypeGoesHere)]),
() => new BaseResponseBuilder<ConcreteTypeGoesHere>()
)
..addPlugin(StandardJsonPlugin()))
.build();
Then re-run flutter packages pub run build_runner build
Makes me wish for Gson... :S
Here is my approach:
class Wrapper<T, K> {
bool? isSuccess;
T? data;
Wrapper({
this.isSuccess,
this.data,
});
factory Wrapper.fromJson(Map<String, dynamic> json) => _$WrapperFromJson(json);
Map<String, dynamic> toJson() => _$WrapperToJson(this);
}
Wrapper<T, K> _$WrapperFromJson<T, K>(Map<String, dynamic> json) {
return Wrapper<T, K>(
isSuccess: json['isSuccess'] as bool?,
data: json['data'] == null ? null : Generic.fromJson<T, K>(json['data']),
);
}
class Generic {
/// If T is a List, K is the subtype of the list.
static T fromJson<T, K>(dynamic json) {
if (json is Iterable) {
return _fromJsonList<K>(json) as T;
} else if (T == LoginDetails) {
return LoginDetails.fromJson(json) as T;
} else if (T == UserDetails) {
return UserDetails.fromJson(json) as T;
} else if (T == Message) {
return Message.fromJson(json) as T;
} else if (T == bool || T == String || T == int || T == double) { // primitives
return json;
} else {
throw Exception("Unknown class");
}
}
static List<K> _fromJsonList<K>(List<dynamic> jsonList) {
return jsonList?.map<K>((dynamic json) => fromJson<K, void>(json))?.toList();
}
}
In order to add support for a new data model, simply add it to Generic.fromJson:
else if (T == NewDataModel) {
return NewDataModel.fromJson(json) as T;
}
This works with either generic objects:
Wrapper<Message, void>.fromJson(someJson)
Or lists of generic objects:
Wrapper<List<Message>, Message>.fromJson(someJson)