How can I create a non-modifiable class Flutter - 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.

Related

Read provider inside a global or static method in Flutter

I have a question, regarding reading providers from inside static methods or global methods. I am using riverpod and awesome_notification packages, and I need to alter the state the app, from the action of the notification, for this, the package uses static methods inside a controller class.
class NotificationController{
...
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction) async {
...//some way to access a provider, to call methods on it
}
...
}
If there is another way of doing this that I am not seeing, please let me know.
I have not been able to find a way to do this.
You can:
Pass to the ref function as a parameter.
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction, Ref ref) async {
final some = ref.read(someProvider);
}
Create a class that accepts the ref field in the constructor.
final notificationProvider = Provider((ref) => NotificationController(ref));
// or use tear-off
final notificationProvider = Provider(NotificationController.new);
class NotificationController {
NotificationController(Ref ref) {
_ref = ref;
}
static late final Ref _ref;
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction) async {
final some = _ref.read(someProvider);
}
}
An additional example:
import 'package:riverpod/riverpod.dart';
final valueProvider = Provider<int>((_) => 5);
final managerProvider = Provider(ManagerProvider.new);
class ManagerProvider {
ManagerProvider(Ref ref) {
_ref = ref;
}
static late final Ref _ref;
static int getValue() => _ref.read(valueProvider);
}
void main() {
final container = ProviderContainer();
container.read(managerProvider);
final value = ManagerProvider.getValue();
print(value); // 5
}
Either way, you should always have access to `Ref'.
Update:
As #OppositeDragon and #Eran Ravid pointed out, we really can't access _ref in a static method. However, if you define _ref in the constructor, it is possible. I think it's a terrible anti-pattern, though. Use method 1 and you will be fine.

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 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.

The instance member 'params' can't be accessed in an initializer

class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
var myTest = params.[comLevel];
}
Error report--"The instance member 'params' can't be accessed in an initializer." I am new to programming and this is being called directly from a widget. I checked the LevelUp map and it has contents. The error occurs where I am trying to assign the param value to myTest. It doesn't matter if I put the key in quotes or provide an integer. Any advice would be greatly appreciated.
You can't access params before you've initialized the object. To fix your example, move your myTest initialization into a constructor.
Also, I don't believe you should have a period before [comLevel].
class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
String myTest;
LevelUp() {
myTest = params[comLevel];
}
}
Null safety update:
Use late keyword: Dart 2.12 comes with late keyword which helps you do the lazy initialization which means until the field bar is used it would remain uninitialized.
class Test {
int foo = 0;
late int bar = foo; // No error
}
Although this question has been answered for the OP's case, I want to offer a solution to those receiving this error in a StatefulWidget scenario.
Consider a situation where you would want to have a list of selectable items that dictate which category to display. In this case, the constructor might look something like this:
CategoryScrollView({
this.categories,
this.defaultSelection = 0,
});
final List<String> categories;
final int defaultSelection;
Note the property defaultSelection is responsible for specifying which category should be selected by default. You would probably also want to keep track of which category is selected after initialization, so I will create selectedCategory. I want to assign selectedCategory to defaultSelection so that the default selection takes effect. In _CategoryScrollViewState, you cannot do the following:
class _CategoryScrollViewState extends State<CategoryScrollView> {
int selectedCategory = widget.defaultSelection; // ERROR
#override
Widget build(BuildContext context) {
...
}
}
The line, int selectedCategory = widget.defaultSelection; will not work because defaultSelection it hasn't been initialized yet (mentioned in other answer). Therefore, the error is raised:
The instance member 'widget' can't be accessed in an initializer.
The solution is to assign selectedCategory to defaultSelection inside of the initState method, initializing it outside of the method:
class _CategoryScrollView extends State<CategoryScrollView> {
int selectedCategory;
void initState() {
selectedCategory = widget.defaultSelection;
super.initState();
}
A simple example, where it shows how we can resolve the above issue,
Example: Create an instance of class B, and pass an instance of class A in the parameter of it
WRONG(Compile time error of initializer):
final A _a = A();
final B _b = B(_a);
shows error: The instance member '_a' can't be accessed in an initializer.
Right:
final A _a = A();
late final B _b;
AppointmentRepository() {
_b = B(_a);
}
#100% working solution
:
Juts place var myTest = params.[comLevel];
below your Build method.
eg.
class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
Widget build(BuildContext context) {
var myTest = params.[comLevel];
}
}
For me it happened Because i was trying to access a Property of a class instance (Lets Say Class A ) And Use this property to initialize Another Class (Class B) , The Property Was Integer Number and Was Defined
However , Since i didn't Make an Object from "Class A" I can access those propertied Belong to it !
I tried to use this property inside the "Build" Method so that an object is "Created/Built" And it Worked !
I also got the similar error.
And I found the solution as follows.
My first code:
final BuildContext mycontext = GlobalContextClass.navigatorKey.currentContext;
final PsValueHolder psValueHolder = Provider.of<PsValueHolder>(mycontext, listen: false);
Next is the code where the error is fixed:
final PsValueHolder psValueHolder = Provider.of<PsValueHolder>(GlobalContextClass.navigatorKey.currentContext, listen: false);
Instead of defining 2 variables in a row, I placed the first variable directly in the place of the 2nd variable.
Another solution is making your variable, a GetX parameter.
int count_myProducts = cartItems.length; //The instance member 'cartItems' can't be accessed in an initializer. (Documentation)
int get count_myProducts => cartItems.length;
see this video at 27:34
GetX State Management tutorial with Flutter
https://www.youtube.com/watch?v=ZnevdXDH25Q&ab_channel=CodeX
Just carry
var myTest = params.[comLevel];
into Widget build{} below.
like this :
class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
Widget build(BuildContext context) {
var myTest = params.[comLevel];
}
}

Flutter object is not initialized after initState

I'm new to flutter, and i bumped into a problem.
I have a Feed model in my app that looks like this:
import 'package:uuid/uuid.dart';
class Feed {
// Static Members
var uuid = new Uuid();
// Members
String id;
bool isScheduled;
DateTime createdTime;
DateTime feedingTime;
String deviceId;
// Constructors
Feed({this.feedingTime, this.deviceId, this.isScheduled}) {
id = uuid.v4();
createdTime = DateTime.now();
}
Feed.fromDevice(deviceId) {
Feed(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
}
}
Now i have my AddFeedForm that i'm trying to initialize with default values, in the InitState:
class _AddFeedFormState extends State<AddFeedForm> {
// Final Members
final _formKey = GlobalKey<FormState>();
final List<Machine> _devices = machinesFromServer;
// Members
Feed _feed;
#override
void initState() {
_feed = Feed.fromDevice(_devices.first.id);
super.initState();
}
But somehow after the initState the _feed parameter stays null!
Any ideas?
But somehow after the initState the _feed parameter stays null!
Are you sure this is the case, and not that you're getting a Feed instance that has null fields?
It looks like your named constructor is incorrect:
Feed.fromDevice(deviceId) {
Feed(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
}
Here you're calling the default Feed constructor inside a named constructor, but not doing anything with the result - this is creating another Feed and then throwing it away. The one returned by the named constructor has not been initialised.
What you probably wanted was this:
Feed.fromDevice(deviceId):
this(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
This makes the fromDevice constructor call the default constructor for initialisation of the instance, rather than creating another copy that goes unused.
Another option would be to make it a static method:
static fromDevice(deviceId) {
return Feed(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
}
There wouldn't be much difference in this case.. Constructors seem nicer, but sometimes you might find that you want to a) make initialisation async (static methods can return a Future<Feed> but constructors cannot or b) do more processing of the arguments before they're passed to the real constructor that might not fit nicely in the initialiser call.