Dart dependency injection of a class with parameters - flutter

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

Related

Modifying Lists inside of classes - Flutter, Riverpod, Hooks

I would like to have a class that contains a list of another class and be able to update it using Riverpod/Hooks. I'm trying to figure out the best syntax or even way to approach the issue.
I'm starting with a StateProvider to make it simpler and thought to use the List method .add() to add to the list but I'm getting the error: "The method 'add' can't be unconditionally invoked because the receiver can be 'null'." or null check operator used on null value (when I add the !)- so I think I'm not writing it correctly and I'm sure it has to do with the Riverpod or even Dart syntax.
Any suggestions or resources would be appreciated. Thanks in advance.
So for example:
Session({this.title, this.sessionThings})
String? title;
List<SessionThings> sessionThings;
}
class SessionThings{
SessionThings({this.title, this.time})
String? title;
double? time;
}
ref.read(buildExSessionProvider.notifier).state = Session()..sessionThings.add(SessionThings(title: 'something', time: 20);
using:
hooks_riverpod: ^1.0.3
flutter_hooks: ^0.18.3
Do not access state outside a notifier. Something like this could work:
class SessionStateNotifier extends StateNotifier<Session?> {
SessionStateNotifier() : super(null);
void create(String title) => state = Session(title: title);
void add(SessionThings things) =>
state = Session(title: state?.title, sessionThings: [...state?.sessionThings ?? [], things]);
}
Use with hooks
class MyApp extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = useState(0);
final Session? session = ref.watch(sessionProvider);
final SessionStateNotifier sessionStateNotifier = ref.watch(sessionProvider.notifier);
return <widget>
}
}
``

Dart Singleton with parameters (global app logger)

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');
...
}
...
}

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];
}
}

Unable to define object inside a statefulwidget

I am trying to store data in an object reference right now it's just a simple class but letter i am replacing it with a singleton class kindly explain why I am not able to initialize the object just above build method.
class MyStatefulWidget1State extends State<MyStatefulWidget1> {
final TextEditingController titleController = TextEditingController();
Data().value = "dscs"; **//IF i define here it will produce error**
#override
Widget build(BuildContext context) {
Data().value = "dscs"; **// bu if i define here it will work just fine**
return TextField(controller: titleController);
}
}
class Data {
String value;
}
In any type of class we can only create variable and method while you are trying to access objects member variable(value) that't why it is giving error.
While build method is also one type of method, so you can access any class or object member variable too. That's why it is working over there.
If you create simple object Data class in MyStatefulWidget1State state then then try to access it's member variable then also you will get same error.
Something like following.
Data c = Data();
c.value = 'f';
But we can do it in any method, so it will work in build method.
You can use initState() for this purpose.
#override
void initState() {
super.initState();
Data().value = "dscs";
}

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.