How to create Hive adapter for XFile class - flutter

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.

Related

How to handle enum in Hive Flutter?

I have an enum in my model class:
MyRepresentation { list, tabs, single }
I have already added an adapter and registered it.
I have given it a proper type id and fields.
It gives error:
HiveError: Cannot write, unknown type: MyRepresentation. Did you forget to register an adapter?
Did you register the enum too or just the model? Say your model file myrepresentation.dart looks like this:
import 'package:hive/hive.dart';
part 'myrepresentation.g.dart';
#HiveType(typeId: 1)
class MyRepresentation extends HiveObject {
#HiveType(0)
final String id;
#HiveType(1)
final Foo foo;
MyRepresentation({required this.id, required this.foo});
}
#HiveType(typeId: 2)
enum Foo {
#HiveField(0)
foo,
#HiveField(1)
bar,
}
Then you generate you type adapters and initialize both of them in your main:
void main() async {
await Hive.initFlutter();
...
Hive.registerAdapter(MyRepresentationAdapter());
Hive.registerAdapter(FooAdapter());
runApp(MyApp());
}
If you have done this and it is still giving you problems, you could try to put the enum in its own file and write its own part statement.
If this still isn't working I suggest simply storing the enum as int yourself in the TypeAdapter read() and write() methods like this:
#override
MyRepresentation read(BinaryReader reader) {
return MyRepresentation(
reader.read() as String,
Foo.values[reader.read() as int]
);
}
#override
void write(BinaryWriter writer, MyRepresentation obj) {
writer.write(obj.id);
writer.write(obj.foo.index);
}

Using Riverpod with Objects - always reinstantiate once property changes?

I am making first steps with Riverpod and just want to check if my understanding of handling changes of some data class properties using Riverpod is correct.
Imagine, I have a data class like that:
class MyClass {
final String name;
final int id;
const MyClass(this.name, this.id);
}
Then I create a StateNotifier:
class MyClassStateNotifier extends StateNotifier<MyClass> {
MyClassStateNotifier(MyClass state) : super(state);
void setName(String name) {
state.name = name;
}
}
And this won't work - UI will not be rebuilt after calling setName this way.
So I need to modify classes in the following way:
class MyClass {
final String name;
final int id;
const MyClass(this.name, this.id);
MyClass copyWith({name, id}) {
return MyClass(name ?? this.name, id ?? this.id);
}
}
and the StateNotifier as following:
class MyClassStateNotifier extends StateNotifier<MyClass> {
MyClassStateNotifier(MyClass state) : super(state);
void setName(String name) {
state = state.copyWith(name: name);
}
}
This pair will work and the UI will be rebuilt.
So, my question: does one always need to reinstantiate the object in this way?..
From my perspective, this is a bit strange (simple datatypes like String / int do not require this) and the boilerplate for copyWith method might become pretty huge if I have a dozen of object's properties.
Is there any better solution available for Riverpod or is it the only one and correct?..
Thanks in advance! :)
To trigger a state change you have to use the state setter. The implementation looks like this:
#protected
set state(T value) {
assert(_debugIsMounted(), '');
final previousState = _state;
_state = value;
/// only notify listeners when should
if (!updateShouldNotify(previousState, value)) {
return;
}
_controller?.add(value);
// ...
The internal StreamController<T> _controller needs to be triggered (add), to notify listeners (in this case riverpod) about updates.
By using state.name = something you're not informing the StateNotifier about a new state (not calling the state setter). Only your object holds the new value but nobody was notified.
Your state is mutable and that very often leads to such misbehavior. By using an immutable object you can prevent such errors in the first place. Write it yourself or use freezed.
Learn more about immutability in my talk

How to Serialize object with XFile in Hydrated Bloc?

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

How can I create a non-modifiable class Flutter

I need to create a class called GeneralAppAndDeviceDetails which has the following fields:
import 'package:flutter/material.dart';
class GeneralAppAndDeviceDetails {
final bool isDarkModeEnabled;
final double deviceWidth;
final double deviceHeight;
final Color primaryColor;
final Color secondaryColor;
}
It basically stores general device and app information at the start of the program so that I don't need to create a new instance of Theme or MediaQuery class whenever I want to access these details.
The problem I'm facing is that how can I write this class so that after the fields' values are assigned, They will be unmodifiable? (so that nothing can change the field values)
(I tried to create a singleton class but I need to pass the values to the constructor and by using factory and private constructor, A user can create new classes with different parameters passed to the factory.)
The thing I need is to have static fields that can receive a value once and become unmodifiable after that. How can I achieve something similar?
Thank you
Update:
I wrote the class as below:
import 'package:flutter/material.dart';
class GeneralAppAndDeviceDetails {
final bool isDarkModeEnabled;
final double deviceWidth;
final double deviceHeight;
final Color primaryColor;
final Color secondaryColor;
static bool _isAlreadyCreated = false;
static GeneralAppAndDeviceDetails _instance;
factory GeneralAppAndDeviceDetails(bool isDarkModeEnabled, double deviceWidth,
double deviceHeight, Color primaryColor, Color secondaryColor) {
if (_isAlreadyCreated == false) {
_isAlreadyCreated = true;
_instance = GeneralAppAndDeviceDetails._(isDarkModeEnabled, deviceWidth,
deviceHeight, primaryColor, secondaryColor);
}
return _instance;
}
const GeneralAppAndDeviceDetails._(this.isDarkModeEnabled, this.deviceWidth,
this.deviceHeight, this.primaryColor, this.secondaryColor);
}
I use a flag to check if an instance was created before or not in here and with this code, a similar instance will be returned every time but is it the best way to achieve this?
This is your singleton class
class Test{
final String str;
static Test _singleton;
Test._internal({this.str});
factory Test(String str) {
return _singleton ??= Test._internal(
str: str
);
}
}
example code for you to try and test
void main() {
Test obj = Test('ABC');
print(obj.str);
Test obj1 = Test('XYZ');
print(obj1.str);
}
class Test{
final String str;
static Test _singleton;
Test._internal({this.str});
factory Test(String str) {
return _singleton ??= Test._internal(
str: str
);
}
}
try running this in dartpad for better understanding
you can make the class as singleton and then make these fields as private, accessible only through getters and setters, inside the setter you can check and discard the new value if there is already some value assigned to the field.

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