How to Serialize object with XFile in Hydrated Bloc? - flutter

I'm using image_picker plugin to get images from gallery.
I save that image(XFile) in an object with bunch of properties. My State of HydratedBloc has list of that object. What is the best way to store that XFile for HydratedBloc?
What is the best way to write toJson and fromJson functions for Expense class.
I have two thoughts
I can convert XFile object to base64 and then convert it back again in fromJson.
I can write Xfile to ApplicationDocumentsDirectory as a File and then get it back from there to tmp storage on fromJson.
At second option, I have to delete permanent file from ApplicationDocumentsDirectory when I get back the file to tmp storage to avoid bloating storage of phone.
Codes are like below.
My Object Expense
class Expense extends Equatable {
final String companyName;
final DateTime? date;
final XFile? image;
...
...
}
ExpenseState
class ExpenseState extends Equatable {
final List<Expense> expenses;
const ExpenseState({required this.expenses});
...
}
ExpenseBloc
class ExpenseBloc extends HydratedBloc<ExpenseEvent, ExpenseState> {
#override
ExpenseState? fromJson(Map<String, dynamic> json) {
return ExpenseState.fromJson(json);
}
#override
Map<String, dynamic>? toJson(ExpenseState state) {
return state.toJson();
}
}

Related

How to create Hive adapter for XFile class

I'm trying to store custom object that has variable storing list of XFile's; cross platform image representation from package called image_picker version 0.8.4+3. When writing an error occurs saying that im missing Adapter for XFile which is understandable, but I'm having hard time deciding how to declare such Adapter for external source file class.
This is my Receipt class that has some list of XFile's.
Note: I've removed nonmeaningful variables from snippet.
#HiveType(typeId: 0)
class Receipt extends HiveObject with EquatableMixin {
Receipt({
List<XFile>? files,
}) {
this.files = files ?? <XFile>[];
}
#HiveField(6)
late final List<XFile> files;
#override
List<Object?> get props => [
files,
];
}
Now I was thinking about two possible solutions; one - copy whole source code from XFile, add HiveType and HiveField decorators and generate adapter from that or two - create class that will extend from XFile and add decorators something like this:
#HiveType(typeId: 1)
class XFileAdapter extends XFile with HiveObjectMixin {
// access fields and add decorators here
}
but I have no clue how to add decorators to these field without overriding every one of them. And even if I knew how to do that, it turns out that XFile's doesn't have its own variables I've could add Hive decorators to, it simply takes paramethers and passes them down to XFileBase class since it further decides what to do with them.
Very similar question has been asked in this thread but the only aswer suggests creating new class from scratch that imitates source class which is not solution to my problem.
I think creating a new MyXFile class as you suggested might be the way to go. But as you've said you will need to override the properties you want to keep in hive. This code seems to be working as intended:
Code
import 'dart:typed_data';
import 'package:image_picker/image_picker.dart';
import 'package:hive_flutter/adapters.dart';
part 'my_xfile.g.dart';
#HiveType(typeId: 1)
class MyXFile extends XFile {
#override
#HiveField(1)
final String path;
#override
#HiveField(2)
final String? mimeType;
#HiveField(3)
final String? _name;
/// The base implementation of `XFileBase.name` throws an
/// [UnimplementedError] so we are overriding it to return a known
/// [_name] value.
#override
String get name {
if (_name != null) {
return _name!;
}
return super.name;
}
#HiveField(4)
final int? _length;
/// The base implementation of `XFileBase.length()` throws an
/// [UnimplementedError] so we are overriding it to return a known
/// [_length] value.
#override
Future<int> length() {
return _length != null ? Future.value(_length!) : super.length();
}
#HiveField(5)
final Uint8List? bytes;
#HiveField(6)
final DateTime? _lastModified;
/// The base implementation of `XFileBase.lastModified()` throws an
/// [UnimplementedError] so we are overriding it to return a known
/// [_lastModified] value.
#override
Future<DateTime> lastModified() {
return _lastModified != null
? Future.value(_lastModified!)
: super.lastModified();
}
MyXFile(
this.path, {
this.mimeType,
String? name,
int? length,
this.bytes,
DateTime? lastModified,
}) : _name = name,
_length = length,
_lastModified = lastModified,
super(
path,
mimeType: mimeType,
name: name,
length: length,
bytes: bytes,
lastModified: lastModified,
);
}
By using this I've been able to save and retrieve my object MyXFile and as it is extending XFile you should be able to use it the same way.
Then instead of having a List<XFile>? files in your Receipt class you will need a List<MyXFile>? files.

How to define proper state class in bloc while using freezed plugin

I am trying to understand, how can I use the bloc pattern (specifically state) properly. I am facing this issue for more than a month, but not able to figure out a solution.
Let's consider, I have defined state class following way,
#freezed
abstract class ExampleState with _$ExampleState {
const factory ExampleState.initial() = Initial;
const factory ExampleState.getDataFromServer() = GetDataFromServer; //Thsi will return the ServerData Object.
const factory ExampleState.useServerData() = UseServerData;
const factory ExampleState.changeServerDataAndUpload() = ChangeServerDataAndUpload;
}
Let's consider our Server Data Model is the following way
class ServerData {
final String userId;
final String firstName;
final String lastName;
final String fullAddress;
final String fatherName;
ServerData(
this.userId,
this.firstName,
this.lastName,
this.fullAddress,
this.fatherName,
);
}
In this example, we are able to see, GetDataFromServer, UseServerData, and ChangeServerDataAndUpload state is sharing the same ServerData object. How should I design my state such that, the same DataModel object can be shared between different states?

Passing Map or Class to widget is better practice?

In my code, I have the Step class:
class Step{
String name;
bool completed;
Step({
#required this.name,
#required this.completed
});
Map<String, dynamic> toMap() => {
'name': name,
'completed': completed
};
}
I use Firebase as my backend and pull data from cloud firestore using StreamBuilder and pass it to my StepView widget:
StreamBuilder(
stream: Firestore.instance.collection('step').snapshots,
builder: (context, snapshot) {
sample_step = snapshot.data.documents[0].data;
return StepView(
step: sample_step
)
}
)
My question is: What is the better practice between two options:
Passing the raw data I pulled from cloud Firestone, which is a Map<String, dynamic>, to my widget:
class StepView extends StatefulWidget {
final Map<String, dynamic> step;
}
Or converting the Map to the Step class and pass that instance of Step class to my widget:
// Convert the Map<String, dynamic> sample_test to class Step
// Need to code the method fromMapToClass in Step class
new_step = Step.fromMapToClass(sample_step)
return StepView(
step: new_step
)
class StepView extends StatefulWidget {
final Step step;
}
That totally depends on how you prefer to code your widgets and which state management scheme you would use.
If I was at your place and I used ProviderModel or any other model for state management:
I would CONVERT the map to class object FIRST
You need to implement a method fromMapToClass in your class to be able to convert the firestore data to the required Step class object.
Your Step class should look somthing like this (the function could change depending upon you data)
class Step {
String name;
bool completed;
Step({#required this.name, #required this.completed});
Map<String, dynamic> toMap() => {'name': name, 'completed': completed};
Step.fromMapToClass(Map<String, dynamic> json) {
this.name = json['name']??"";
this.completed = json['completed']??false;
}
}
Then send the Step class object to my Widget Class (or StateManagement class which in ProviderModel case will be the Provider class that extends ChangeNotifier).
This way your widget will not have to handle the conversion part and can just focus on the design and implementation.

Using a provider in a custom class flutter

I am creating a program that needs to use Provider to get values. I call my provider like below in a stateful widget
final user = Provider.of<Users>(context);
Now I would like to use the provider in a custom class
class UserInformation {
final user = Provider.of<Users>(context):
}
This won't work because context is not defined. kindly assist on how I can do this without using a BuildContext.
This is my class Users that I have on a separate dart file and use as a model for for my data streams.
class Users {
final String uid;
final String name;
final String department;
final String position;
Users({ this.uid, this.department, this.name, this.position });
}
This is the query I use to pull data from firestore
Stream<List<FormQuestions>> get userData{
return userCollection.where('name', isEqualTo: 'First Name').where('department', isEqualTo: 'department').snapshots()
.map(_userDataFromSnapshot);
}
I would like the name to be a variable that I get from say (user.name) from the model class. and the same for the department.
Thanks.
You can only access classes which are ChangeNotifiers in the descendant widgets in the tree below this provided ChangeNotifier because behind the scenes the Provider uses InheritedWidget (which uses context) to provide you with the ChangeNotifier you put up in the tree
So in your case there is no way to access the Users from UserInformation and you have to alter your code to make a workaround
Edit: this is a suggestion to achieve what you want if you are using this code inside a widget:
class UserInformation{
final User user;
UserInformation(this.user);
}
class SomeWidget extends StatefulWidget {
#override
_SomeWidgetState createState() => _SomeWidgetState();
}
class _SomeWidgetState extends State<SomeWidget> {
void someMethod(){
final User user = Provider.of<Users>(context);
UserInformation userInformation = UserInformation(user);
//do something
}
#override
Widget build(BuildContext context) {
return Container();
}
}
¡Hey! In the class you need to add "with ChangeNotifier":
class Users with ChangeNotifier {
final String uid;
final String name;
final String department;
final String position;
Users({ this.uid, this.department, this.name, this.position });
}
Hope help. Sorry for the english, yo hablo español. :D

Can Provider handle state of a complexed class such as this? If not, how can I make this class available to my entire app in its global state?

I would like make a Currency object (shown below) shared throughout my entire app using global state (perhaps this isn't the best way?).
I have tried using the Provider package but I cannot seem to get it working.
class Currency extends ChangeNotifier {
String baseCurrency = 'USD';
Map<String, double> rates = {};
List<String> currencList = [];
Future<Map<dynamic, dynamic>> getLatestCurrencyData() async {
NetworkHelper networkHelper = NetworkHelper('$url_fixer_io$api_key');
var currencyData = await networkHelper.getData();
return currencyData;
}
void populateRates() async {
Map<dynamic, dynamic> currencyData = await getLatestCurrencyData();
Map<String, dynamic> newRates = currencyData['rates'];
newRates.forEach((curr, rate) {
this.currencList.add(curr);
this.rates.putIfAbsent(curr, () => rate.toDouble());
});
notifyListeners();
}
Future<List<String>> getCurrencyList() async {
if (currencList.length == 0) {
await populateRates();
}
return currencList;
}
void changeBaseCurrency(String newCurrency) {
baseCurrency = newCurrency;
notifyListeners();
}
}
The above implementation gives me the following error:
flutter: Another exception was thrown: Tried to use Provider with a subtype of Listenable/Stream (Currency).
I have limited experience managing state on reactive platforms such as flutter, and any tips on how I could accomplish this would be very much appreciated!
It does not matter how complex your class is.
If your class extends ChangeNotifier
Then your class should be made available to the tree by using either
ChangeNotifierProvider
or
ListenableProvider
My guess is you were using Provider instead of this two, which has an assertion set to throw if used with Listenable or Stream(ChangeNotifer is Listenable)
This is a portion of code from Provider class
assert((){
if (value is Listenable || value is Stream) {
throw FlutterError(''')
Instead of Provider use ChangeNotifierProvider or ListenableProvider. The difference is that ChangeNotifierProvider automatically calls the dispose method while you override the dispose method in ListenableProvider