Common interface for two Flutter plugins - flutter

I have two Android specific Flutter plugins. They are for two custom devices to access the same hardware with different platform specific SDKs.
I have successfully implemented both as Flutter plugins. I want use these in Flutter application and use the plugin based on the device.
I have created a common abstract class to expose same API but the flutter plugin class has all static methods which doesn't allows to implement a common interface.
How can we expose a common dart implement from a plugin and use it interchangeably.
For an example let say we have this abstract class as the common interface,
abstract class Pluggable{
void plug();
}
The plugin class which is generated by Flutter create is,
import 'dart:async';
import 'package:flutter/services.dart';
class MyPlugin {
static const MethodChannel _channel = MethodChannel('my_plugin');
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static Future<void> plug() async {
await _channel
.invokeMethod('plug');
}
}
The way of Flutter plugins to have static methods which can not be overridden.

The problem is that your flutter plugin class has abstract methods. There are many ways to do this, from using a proper dependency injection framework to have the right type injected, to using a Provider or other InheritedWidget wrapper to hold the instance of the right plugin class and expose it in a common way.
However, the simplest way is to use a Singleton - assuming that this is something that that gets instantiated right at the beginning of the app running and is used throughout.
The way I'd recommend is to add a static initializer to your singleton. See the example below.
// this declares the abstract class.
abstract class MyAbstractClass {
static void initializeWith(MyAbstractClass instance) {
_instance = instance;
}
// this could introduce a potential bug if you don't initialize
// or try to do it multiple times, so make sure you do that
// properly exactly once.
static late final MyAbstractClass _instance;
static MyAbstractClass get instance => _instance;
// methods to show how this works
void method1();
void method2();
// an example of how to call directly from the class
static void doMethod1() => _instance.method1();
}
// one simple implementation
class MyClass1 implements MyAbstractClass {
#override
void method1() => print(1);
#override
void method2() => print(2);
}
// another simple implementation
class MyClass2 implements MyAbstractClass {
#override
void method1() => print("a");
#override
void method2() => print("b");
}
// and in practice, you simply have to initialize and then
// use however you'd like.
void main() {
// MyAbstractClass.initializeWith(MyClass1());
// MyAbstractClass.doMethod1();
// MyAbstractClass.instance.method2();
MyAbstractClass.initializeWith(MyClass2());
MyAbstractClass.doMethod1();
MyAbstractClass.instance.method2();
}
You'd have to convert all of your static methods to members but that should be as simple as removing any static references and removing the static keywords.

Related

Is it possible to create enum singletons in Dart?

Is it possible to create enum-based singletons in Dart, as in Java?
With Dart 2.17 we have enhanced enums supporting instance fields and methods with functionality.
Below is a simple example, and check this DartPad Link to test it yourself.
enum EnumSingleton {
_instance;
static EnumSingleton getInstance() => _instance;
final String name = "Enum Singleton";
void speak() => print('I am an $name');
}

What is the best way to define singleton properties in Flutter/Dart?

I am using flutter_easyLoading package for loaders in my flutter project. It says in the documentation that it creates a singleton and I have to define its properties only once somewhere and it would be available throughout the app. What is the best practice to define these properties?
Right now I am initializing its variables in a splash screen file like this.
class _SplashScreenState extends State<SplashScreen> {
#override
void didChangeDependencies() async {
super.didChangeDependencies();
EasyLoading.instance
..displayDuration = const Duration(milliseconds: 2000)
..indicatorType = EasyLoadingIndicatorType.fadingCircle
..loadingStyle = EasyLoadingStyle.dark;
}
Should I do it this way or maybe define some util method for all these properties.
You can use a factory constructor implement singleton classes in dart.
This is a simple example adapted to this context
class EasyLoadingSingleton {
static final EasyLoadingSingleton _easyloading = EasyLoadingSingleton._internal();
factory EasyLoadingSingleton() {
return _easyloading;
}
EasyLoadingSingleton._internal();
}

How to access/inject ObjectBox database in repository in Flutter - Reso Coder DDD

all the examples, I have seen, initialize ObjectBox in a State(less/full)Widget. I am using a layered architecture (currently refactoring to DDD) and wonder, how to inject my ObjectBox properly.
In my repository, I inject the data sources using the injectable and the getit packages with
#injectable
#LazySingleton (as: IJournalsRepository)
class JournalsRepository implements IJournalsRepository {
final JournalsRemoteDataSource journalsRemoteDataSource;
final JournalsLocalDataSource journalsLocalDataSource;
JournalsRepository(this.journalsLocalDataSource, this.journalsRemoteDataSource);
Those packages then create an instance of JournalsRemoteDataSource and of JournalsRemoteDataSource and inject it into the repository.
The ObjectBox example shows for initialization
class _MyHomePageState extends State<MyHomePage> {
Store? _store;
#override
void initState() {
super.initState();
openStore().then((Store store) => _store = store;);
}
#override
void dispose() {
_store?.close(); // don't forget to close the store
super.dispose();
}
}
So I am lacking an idea on how an injector could initialize ObjectBox or how I could access the objectBox object from within the injected JournalsRemoteDataSource if I would initialize objectBox in MyApp() (which is upstream to the HomePage)
PS: reopening the box in JournalsRemoteDataSource on every read/write event has a very poor performance
========== UPDATE ==========
supplementing my comment to #vaind
I have found your answer on this similar question in the meantime (not sure why I did not see it, initially). I hope to get this approach working here, too. However, I have still issues initializing the store. My prototype comes from Firestore and looks like this:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:injectable/injectable.dart';
#module
abstract class FirebaseInjectableModule {
#lazySingleton
FirebaseAuth get firebaseAuth => FirebaseAuth.instance;
}
though I do not understand where the getter firebaseAuth comes from and haven't found any explanation, yet. Anyways, I adapted that to
import 'package:injectable/injectable.dart';
import 'package:objectbox/objectbox.dart';
import 'package:test/objectbox.g.dart';
#module
abstract class ObjectboxInjectableModule {
#lazySingleton
Future<Store> get store async => await openStore();
}
and use this with
#LazySingleton (as: ILocalDataSource)
class ObjectBoxDataSource implements ILocalDataSource {
final Store _store;
final Box<JournalOboxEntity> _box;
ObjectBoxDataSource(this._store) : _box = _store.box();
Besides final Store _store being grey in IntelliJ (unused variable), I receive the error
You tried to access an instance of Store that is not ready yet
'package:get_it/get_it_impl.dart':
Failed assertion: line 404 pos 9: 'instanceFactory.isReady'
So following another answer of vaind, I implemented this as follows. My architecture follows a merge of Reso Coder's DDD and Clean Architecture tutorials. Basically it is DDD with the local/remote data source layer of Clean Architecture.
INFRASTRUCTURE directory
abstract data sources
abstract class ILocalDataSource {
Future<JournalDto> getJournal(int id);
Future<void> storeJournal(JournalDto record);
}
abstract class IRemoteDataSource {
Future<JournalDto> getJournal(int problemClassId);
}
data source implementation
#LazySingleton (as: ILocalDataSource)
class ObjectBoxDataSource implements ILocalDataSource {
final Store _store;
final Box<JournalOboxEntity> _box;
ObjectBoxDataSource(this._store) : _box = _store.box();
injectable module in infrastructure/core
#module
abstract class ObjectBoxInjectableModule {
#preResolve // <<<<<<<<<<<<< needed for async init
#lazySingleton
Future<Store> get store async => await openStore();
}
And now the trick to get it work: My later errors where caused by an injector init not yet finished. After changing injection.dart in the root folder to a Future and awaiting the call in main(), it worked. injection.dart now looks like this:
final GetIt getIt = GetIt.instance;
#injectableInit
Future<void> configureInjection(String env) async {
$initGetIt(getIt, environment: env);
}
I don't have experience with packages get_it & injectable, but from the docs, I think the following alternatives would work. Using get_it directly, not sure about the right way to achieve the same with injectable (generator for get_it) but I guess if you're familiar with it you can configure it to generate the same code.
Alternative A, lazy (async) singleton
GetIt.I.registerSingletonAsync<Store>(openStore);
Alternative B, setup in main(), probably preferrable
change your main to sth like:
void main() async {
GetIt.I.registerSingleton<Store>(await openStore());
runApp(MyApp());
}
Note: Looks like get_it provides a way to reset, which would result in reopening the same store. To avoid issues if you use that, you'd also need to implement a version of get_it's dispose that calls store.close().

How can I access an instance of an object in different classes / pages without a widget tree context?

I am trying to access an instance of an RtcEngine object for AgoraIO from another class/page that doesn't have a widget tree, and therefore no context to refer to with Provider.
First I'm calling initPlatformState() from this class in order to initialize the RtcEngine engine:
class Game extends StatefulWidget {
#override
_GameState createState() => _GameState();
}
class _GameState extends State<Game> implements GameListener {
#override
void initState() {
super.initState();
Agora().initPlatformState(widget.playerId);
}
}
initPlatformState initializes the RtcEngine by creating an instance called engine that I need to use later on to call other methods. This class also contains the method I want to call later using the same instance to adjustVolume...
class Agora {
RtcEngine engine;
// Initialize the agora app
Future<void> initPlatformState(int playerId) async {
RtcEngine engine = await RtcEngine.create(APP_ID);
}
void adjustVolume(int uid, int volume) {
engine.adjustUserPlaybackSignalVolume(uid, volume);
}
}
This is the class that I want to call adjustVolume from. I was considering using Provider to pass the instance to this class but it extends another class and it doesn't have a widget tree with context so I'm not sure how thats possible or if there is a better way.
class Remote extends Component {
final int id;
Remote(this.id);
#override
void update() {
//this is where I'm trying to access the engine instance that was created to call adjustUserPlaybackSignalVolume method
}
}
Any suggestions on how to reuse that instance of "engine" given my situation would be greatly appreciated!

How to access it's static constant from instance of an object?

I have an object that has static constant which I need to reach from its instance.
class ChatsScreen extends StatefulWidget {
var arguments;
static const name = ADatas.chatRoute;
ChatsScreen(this.arguments);
createState() => ChatsScreenState();
}
In above class' State object, I want to call static const name. Above class' State object's code:
class ChatsScreenState extends State<ChatsScreen> with RouteHelper{
String userName = "";
var textEditingController = TextEditingController();
#override
void initState() {
getRouteName(widget); //=> as I understand and see on the VSCode, its the ChatsScreen object.
super.initState();
}
}
I'm trying to implement an interface so I don't know the actually class name while writing the interface. And I thought that I can reach its static constant if I know its actual class. And I wrote something like this but it seems not to be possible. I guess I have a misunderstanding.
class RouteHelper{
String getRouteName(dynamic instance){
if(instance is StatefulWidget){
return instance.runtimeType.name; // => !!!
}
}
}
Note: I'm not trying to get the route name in actual. It's just a concept that i used in this question, so please don't refer better way to get the route name in flutter.
You can't do it like that, people have talked about this in this issue.
However you can kinda do it using class members and typing system.
abstract class Routed {
String getClassRoute();
}
class ChatsScreen extends StatefulWidget implements Routed {
var arguments;
static const name = "myexampleroutename";
ChatsScreen(this.arguments);
createState() => ChatsScreenState();
#override
String getClassRoute() {
return ChatsScreen.name;
}
}
class RouteHelper {
String getRouteName(Routed instance) {
return instance.getClassRoute();
}
}
I said you can't, but with dart:mirrors it is possible, however it is banned on Flutter packages. There is reflectable package that tries to fix that using code generation, but I am not aware of it's status/reliability.