Dart Singleton with parameters (global app logger) - flutter

I'd like to use the Logger library in my app. I only want to instasiate it once so that I have consistent formatting across my app, instead of passing PrettyPrinter in each file I use it. What is the right way of doing this? Should I just use a global const? Is there a way to do this using a Singleton?

I define a helper function to get the Logger:
import 'package:logger/logger.dart';
import 'log_printer.dart';
Logger getLogger(String className) {
return Logger(printer: SimpleLogPrinter(className));
}
Then in each class where I want logging for eaxmple:
class ProfileService with ChangeNotifier {
static final _log = getLogger('ProfileService');
Future<Profile> updateProfile(Profile profile) async {
_log.v('updateProfile');
...
}
...
}
Another example:
class AuthScreen extends StatelessWidget {
final log = getLogger('AuthScreen');
#override
Widget build(BuildContext context) {
log.v('build called');
...
}
...
}

Related

How do I update Flutter's Riverpod values from business logic?

When using Flutter and Riverpod, how do I update its values from my business logic?
I understand that I can get and set values from the UI side.
class XxxNotifier extends StateNotifier<String> {
XxxNotifier() : super("");
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier();
});
class MyApp extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
// getValue
final String value = ref.watch(xxxProvider);
// setValue
context.read(xxxProvider).state = "val";
return Container();
}
}
This method requires a context or ref.
How do I get or set these states from the business logic side?
Passing a context or ref from the UI side to the business logic side might do that, but I saw no point in separating the UI and business logic. Perhaps another method exists.
Perhaps I am mistaken about something. You can point it out to me.
You can pass ref in your XxxNotifier class:
class XxxNotifier extends StateNotifier<String> {
XxxNotifier(this._ref) : super("");
final Ref _ref;
void setNewState() {
state = 'to setting';
// use `_ref.read` to read state other provider
}
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier(ref);
});
// or using tear-off
final xxxProvider = StateNotifierProvider<XxxNotifier, int>(XxxNotifier.new);
You can create methods in your XxxNotifier class to modify the state of your provider.
For example, your notifier class can look like this.
class TodosNotifier extends StateNotifier <List<Todo>> {
TodosNotifier(): super([]);
void addTodo(Todo todo) {
state = [...state, todo];
}
}
You can then read the provider in a callback.
ref.read(xxxProvider.notifier).addTodo(todo);

Flutter - How to retrieve an implemented class with Get.find after registered with Get.put using GetX?

I have a class called GetConnectApiHelper that implemented an abstraction called IApiHelper, I need to register this class with Get.put inside Bindings and retrieve the implementation inside an abstraction variable but when I try to do that I get an error about "the abstraction is not registered".
How can I inject the dependency correctly making it easy to change in case I need to replace with http, dio etc...(clean architecture)
abstract class IApiHelper {}
class GetConnectApiHelper extends GetxService implements IApiHelper {}
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put(GetConnectApiHelper());
Get.put(SignInController());
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper = Get.find(); // This throws the exception
}
======== Exception caught by widgets library =======================================================
The following message was thrown building Builder(dirty):
"IApiHelper" not found. You need to call "Get.put(IApiHelper())" or "Get.lazyPut(()=>IApiHelper())"
I found a solution. I can set the Interface as a Type and then register the implementation I want to be retrieved.
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put<IApiHelper>(GetConnectApiHelper());
Get.put(SignInController());
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper = Get.find();
}
print(apiHelper.runtimeType); // it prints Instance of 'GetConnectApiHelper'
Or I can inject the implementation.
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put<IApiHelper>(GetConnectApiHelper());
Get.put(SignInController(apiHelper: Get.find()));
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper;
SignInController({required this.apiHelper})
}
GetX finds its dependencies based on its exact types so you need to use Get.find<GetConnectApiHelper>()
updated:
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put(GetConnectApiHelper());
Get.put(SignInController<GetConnectApiHelper>());
}
}
class SignInController<T extends IApiHelper> extends GetxController {
final IApiHelper apiHelper = Get.find<T>();
}

Alias one imported class in Dart

I want to create a base class for my Flutter widgets:
import 'package:flutter/widgets.dart';
abstract class State extends State {
#override
Widget build(BuildContext context) {
// ...
}
// ...
}
Like this, it results in the error "State' can't extend itself." which makes perfect sense. To fix it I can do this:
import 'package:flutter/widgets.dart' as base;
abstract class State extends base.State {
But now I have to prefix all other framework classes with "base.". Is there a way to alias just the State class and use everything else without a prefix?
This seems to have worked:
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as base show State;
abstract class State<T extends StatefulWidget> extends base.State {
#override
Widget build(BuildContext context) {
// ...
}
}

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!

Dart dependency injection of a class with parameters

I am trying to learn about dependency injection and trying to use it in Flutter/Dart. I have gone through the library get_it which I find very useful.
But I am having a doubt about the dependency injection in a specific situation.
If I have a Dart class with parameters like this one for example:
class MyClass(){
final String name;
MyClass({this.name})
....
....
}
In such a class, with parameters, It seems like I cannot use dependency injection? or at least the following using get_it will not work:
**** service_locator.dart ****
import 'package:get_it/get_it.dart';
GetIt locator = GetIt();
void setupLocator() {
locator.registerLazySingleton<MyClass>(() => MyClass());
}
This gives error on => MyClass()....since it is expecting a parameter.
How to do this kind of injection?.
You just pass the argument you want to MyClass(). You don’t have to do it inside setUpLocator(). Register the singleton, anywhere in your program, when you know what argument to pass.
For example, if you need to register a user object as a singleton, you’ll have to do it after the user logs in and all their info is available in order to properly instantiate your User class.
because it is a singleton you only need to call myClass.name = 'name' once, anywhere in your app and it will change everywhere.
example app:
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
GetIt.instance.registerLazySingleton<MyClass>(()=> MyClass());
// Name from a future
get('https://jsonplaceholder.typicode.com/users').then((response){
if(response.statusCode==200) {
setState(() {
GetIt.I.get<MyClass>().name = jsonDecode(response.body)[0]['name'];
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: Container(
child: Center(
child: Text(GetIt.I.get<MyClass>().name),
),
),
);
}
}
class MyClass{
String name = '';
}
another way is that because it's built using a lazy builder, the MyClass object will only be built after the first locator.get<MyClass>() is called so you can prepare and get it from a static variable.
There is no issue passing a string into the constructor of your class. You only need access to the value you want to pass into the MyClass constructor. One way to do that is to get the value from another dependency. In this example, you can grab it from AppOptions.
import 'package:ioc_container/ioc_container.dart';
class MyClass {
final String name;
MyClass({required this.name});
}
class AppOptions {
String get myClassName => 'Bob';
}
void main(List<String> arguments) {
final builder = IocContainerBuilder()
..addSingletonService<AppOptions>(AppOptions())
..addSingleton(
(container) => MyClass(name: container<AppOptions>().myClassName));
final container = builder.toContainer();
final myClass = container<MyClass>();
print(myClass.name);
}
Grab the package here
This prints
Bob