Flutter, when to use Factory fromJson and constructor fromJson - flutter

I've been struggling with this for a long time.
For sure, what I currently know is that you should use a factory or static fromJson when you need only one object and a Constructor named .fromJson when you need to create multiple instances.
So.. when?? when we need a one instance and when we need multiple instances??
I'm creating a model class for API response right now, and I'm deeply troubled about whether to use the factory or not.

Factory constructor allows returning already created instances. It allows us easily make singletons and multitones. From the call side, it looks like the usual constructor, but from inside implementation, it varies. Also, the factory constructor doesn't force you to return only one instance (object) as you stated. You can create as many as you need. It allows returning already created instances. That's the difference with an ordinary constructor that always returns a new instance. So this feature gives us some flexibility and in some cases performance improvements.
An example:
class Logger {
static Logger _instance;
Logger._() {
print('Logger created');
}
factory Logger() {
return _instance ??= Logger._();
}
void log(String msg) => print('${DateTime.now()}: $msg');
}
void main() {
A().initialize();
B().initialize();
}
class A {
Logger _logger;
void initialize() {
_logger = Logger();
_logger.log('A initialized');
}
}
class B {
Logger _logger;
void initialize() {
_logger = Logger();
_logger.log('B initialized');
}
}
If we run this code it will produce output like that:
Logger created
2021-09-27 21:59:23.887: A initialized
2021-09-27 21:59:23.887: B initialized
Where you can see that only one instance of Logger class has been created. Despite from calling side we've requested to create two instances.
In most cases, if your task it to create a modal class for API response an ordinary constructor with a static fromJson method is enough.

Related

Flutter plugin instance with parameters

I am trying to make a plugin for authentication. It will act like a "wrapper" (but with additional functionality for each plugin) for different already existing packages for different platforms.
By the additional functionality I mean
Web implementation already has state management
Android implementation is AppAuth, so here I need to add my own state management
I am trying to use the PlatformInterface way, but I cannot find any guide how to make it instantiable with constructor params.
Let's say I have this code:
abstract class KeycloakAuth extends PlatformInterface {
KeycloakAuth()
: super(token: _token);
static final Object _token = Object();
static late KeycloakAuth _instance = KeycloakAuth._setPlatform();
static KeycloakAuth get instance => _instance;
static set platform(KeycloakAuth instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
factory KeycloakAuth._setPlatform() {
if (Platform.isAndroid) {
return KeycloakAuthAppAuth();
} else {
throw UnimplementedError('The current platform ${Platform.operatingSystem} is not supported.');
}
}
}
This would work, but in my app I would like to do this:
final _keycloakAuth = KeycloakAuth(keycloakUrl: 'xxx', clientId: 'yyy');
If I simply tried to add params like this:
abstract class KeycloakAuth extends PlatformInterface {
final String keycloakUrl;
final String clientId;
KeycloakAuth({
required this.keycloakUrl,
required this.clientId,
})
: super(token: _token);
...
I wouldn't be able to pass them inside the _setPlatform() method since this is static method.
Also KeycloakAuthAppAuth extends KeycloakAuth, so I would need to pass those parameters back and forth, like instantiate KeycloakAuth, this instantiates KeycloakAuthAppAuth which needs to pass those parameters back via super()...
Any tips for this?
I know I can make a method initialize({String keycloakUrl, String clientId}) with my parameters, but I would still like to know if it's possible to make it using the constructor.

How to recreate singleton instance if different params are passed to the constructor in dart

I gathered the following understanding for creating a singleton in dart with params
class Foo extends ChangeNotifier {
late String channel;
void instanceMemberFunction () {
print('Foo created with channel $channel')
}
static final Foo _instance = Foo._internal();
Foo._internal() {
instanceMemberFunction();
}
factory Foo({
required String channel
}) {
_instance.channel = channel;
return _instance;
}
}
and I am calling the instance like so
Foo({channel: "bar"})
Now I want to have some working that if I use
Foo({channel: "baz"})
Then a new instance is created and it's okay in that case to destroy the old one. How can I achieve this in dart?
It seems like you've copied some existing example for creating a singleton without fully understanding what it's doing and why. The core parts are:
The single instance is stored in a global or static variable.
The class has one or more public factory constructors that returns that global/static variable, initializing it if necessary.
All other constructors for the class are private to force consumers to go through the factory constructors.
Therefore, if you want your factory constructor to replace its singleton based on its argument, you need to:
Make your factory constructor check if the argument is appropriate for the existing instance. If it is, return the existing instance. If not (or if there is no existing instance), create and return a new instance.
Since you need to check if the existing instance is initialized, make it nullable. (You alternatively could initialize it to a non-null sentinel value, e.g. Foo._internal(channel: '').
Pass the argument along to the private constructor.
class Foo extends ChangeNotifier {
final String channel;
void instanceMemberFunction () {
print('Foo created with channel $channel');
}
static Foo? _instance;
Foo._internal({required this.channel}) {
instanceMemberFunction();
}
factory Foo({required String channel}) {
if (channel != _instance?.channel) {
_instance = Foo._internal(channel: channel);
}
return _instance!;
}
}
Note that this implementation will create a new object if the constructor argument changes, which isn't very singleton-like. Depending on what you want to do, you could:
Return a new object (which could allow multiple simultaneous instances).
Return the existing object.
Return the existing object, but mutate it with the constructor argument.

why should i make the returned instance of factory a static field?

Creating a function that returns an instance of the class is right, but creating a factory that returns an instance of the class should be a static field. why should i make the returned instance of factory a static field?
the code is:
class DBHelper{
DBHelper._();
factory DBHelper()=>instance; // ->> cursor error
// static
// final
DBHelper instance=DBHelper._();
int number=2;
int fun()=>number;
}
I think you didn't quite get the purpose of factory methods. The example you've posted is the way of using singleton pattern in dart/flutter. It's, in my opinion the cleanest way of creating a singleton class.
Now the factory method can be used to to simplify the construction of objects and they don't have to return static fields at all. Here are a few examples:
class OneObject {
final int one;
final String two;
final double three;
OneObject(this.one, this.two, this.three);
factory OneObject.withoudTwo(int one, double three) {
return OneObject(one, "2", three);
}
factory OneObject.fromJson(Map<String, dynamic> aMap) {
return OneObject(aMap["one"], aMap["two"], aMap["three"]); // this is bad use of json parsing
}
}
In your case the DBHelper._(); is the actual constructor of the class and the _ makes it private and this allows you to create a factory method called DBHelper.();
To elaborate the answer a bit more, factory methods are similar to static methods and static methods can't access instance members of the class that they are declared on, they can access only static members. In your example, the instance property (without static) will be created only when a new instance of the DbHelper is created and because of that, returning it from a factory method is impossible because it's not available yet.

Why static map does not retain the value in future methods?

I have declared a map as a instance variable like this:
public class JSONParser_GET {
public static Map<String, String> mapOfValues= new Map<String,String>();
...
Now, I have a future method where I am adding the values to this map:
#future(callout=true)
public static void getRequest(String type,String e,String authHeader){
mapOfValues.put(type,lookupname);
...
But the values are not available when I try to access it in different method. Isn't that is what static is supposed to do?
As I am declaring it as static instance variable it should be globally available. I tried to use global keyword as well. But that also doesn't seems to work.
Any help is appreciated
I think, it's related to the Future annotation. That means, the method getRequest() will be executed asynchronously, not with all usual methods.
So, here is what I did..
It seems future methods may execute in parallel due to which it is not possible to get the values from the response in one Map itself.
So I wrote a method before the getRequest and made it as future so that I can make only one instance of future handler.
#future(callout==true)
public class JSONParser_GET {
public static Map<String, String> mapOfValues= new Map<String,String>();
getRequest()
getRequest()
getRequest()
}
public void getRequest{...}

Dagger 1 to 2 migration - Members injection methods may only return the injected type or void

Im trying to migrate our current system from dagger 1 to 2 and I been stuck for half a day on this. I don't think I'm understanding this well.
Here is my module:
public class BaseModule {
private final Context context;
private final SharedPreferences rawSharedPreferences;
public BaseModule(
Context context,
#Named("RawPreferences") SharedPreferences rawSharedPreferences
) {
this.context = context;
this.rawSharedPreferences = rawSharedPreferences;
}
#Provides
#Singleton
public Context provideContext() {
return context;
}
#Provides
#Singleton
public DevicePlatform provideDevicePlatform(AndroidDevicePlatform devicePlatform) {
return devicePlatform;
}
#Provides
#Named("RawPreferences")
#Singleton
public SharedPreferences provideRawSharedPreferences() {
return rawSharedPreferences;
}
#Provides
#Named("RawPreferencesStore")
#Singleton
public SharedPreferencesStore provideRawSharedPreferencesStore(
#Named("RawPreferences") SharedPreferences sharedPreferences) {
return new AndroidSharedPreferencesStore(sharedPreferences);
}
And my component:
#Singleton
#Component(
modules = {BaseModule.class}
)
public interface BaseComponent {
void inject (DefaultClientController defaultClientController);
void inject (StatisticsProvider statisticsProvider);
Context provideContext();
AndroidDevicePlatform provideDevicePlatform(AndroidDevicePlatform devicePlatform);
SharedPreferences provideRawSharedPreferences();
SharedPreferencesStore provideRawSharedPreferencesStore(
#Named("RawPreferences") SharedPreferences sharedPreferences);
}
I keep getting this error in provideRawSharedPreferencesStore when I run it:
Error:(168, 28) error: Members injection methods may only return the injected type or void.
I dont understand why. Can someone please help me out. Thanks!
A component can contain 3 types of methods:
inject something into some object, which is the error you see. Those methods usually return void, but you can just return the same object, if you try to have something like a builder.
MyInjectedObject inject(MyInjectedObject object); // or
void inject(MyInjectedObject object);
Subcomponents, for which you would include the needed modules as parameters (if they require initialization)
MySubcomponent plus(MyModuleA module);
and basically just "getters" or correctly called provision methods to expose objects, to manually get them from the component, and to your subcomponents
MyExposedThing getMything();
Which one of those is this?
// the line you get your error:
SharedPreferencesStore provideRawSharedPreferencesStore(
#Named("RawPreferences") SharedPreferences sharedPreferences);
You are already providing the SharedPreferencesStore from your module. There you also declare its dependency on RawPreferences: SharedPreferences. You do not have to do this again in your component.
It seems you just try to make the SharedPreferencesStore accessible, as described in 3.. If you just depend on it within the same scope / component, you could just remove the whole component. If you need the getter, you should just remove the parameter. Your Module knows how to create it.
SharedPreferencesStore provideRawSharedPreferencesStore(); // should work.