New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
hi, cannot inject new instance... #52
Comments
Thanks for try states_rebuilder and reporting an issue. 👏 If you inject many instances of the same model (ex: Foo), they all would be registered by the same type (Foo). When you get the mode you will use As a solution, you have to use a custom name to distinguish between them. ListView.builder(
itemCount: yourlist.lenght,
itemBuilder: (context, index) {
return Injector(
inject: [Inject(() => Foo(), name: 'Foo$index')],
builder: (_) => MyChildWidget(index),
);
},
);
) and in your MyChildWidget with has the index a field: final foo = Injector.get<Foo>(name:'Foo$index'); I hope this helps. If not it would help me if you explain your use case by a simple representative code example. |
@GIfatahTH Hello. I have the same exactly problem and I have a use case where I do need to register multiple injections for the same type. I looked through the code and adding a parameter that allows us to opt-in to multiple injections of the type will be a good idea. The default action of returning the last registered instance works perfectly (this is the currently implementation). The use case is when a common project is being used as a package in another project. This would allow the base common project to register the basic dependencies and the specific project can register the dependency again essentially overriding the first registration. i.e.: inject: <Inject<dynamic>>[..._getCommonGlobalState(), ..._globalDependencyOverrides], |
This is a good idea 👍
Your case looks different from @gustavobrian 's. BUT, if one overrides a model, no one can get the old registered instance. |
Thanks!!! GIfatahTH my code: @OverRide
} //build when the widget repeat in the list: If you are testing the widget set [Injector.enableTestMode] to trueIt is possible that my widget with its key is duplicated on the screen, but when it duplicates it does not instantiate the singleton injected, but it gives an error ... Thanks again for your work! |
If I add enabletestMode = true the error disappears, but I wait for your comment to know if something I am doing wrong. Widget build(BuildContext context) { |
I do not know what exactly you are doing and why you are doing it this way. BUT as you said :
you are using the same id in some cases, which means you are injecting ApiMultimedia with the same name in those cases, and this what triggers the error. Names should be unique.
The error disappears fro sure but is your code still work as you intended. What exactly If your code works when setting |
@GIfatahTH One way to resolve this issue is to of course allow a parameter to opt-in to the multiple injections of the same type and then allow getting it using another api of List<TType> allInstances = Injector.getAll<TType>();
List<ReactiveModel<TType>> allReactiveInstances = Injector.getAllAsReactive<TType>(); If the option for opting in is true, and the user has registered multiple of the same type, and the regular |
Thanks but I'm doing wrong, I give two examples, the first one does not update the widget to the changes, and the second one does update. It is the only instantiated widget.
|
That's absolutely feasible, But what is the use case of List List<ReactiveModel>, how can the user know which instance to use from the list? |
661/5000 void _unsubscribe () { there is a removeObserver in states_rebuilder that I don't know why shoot with the widget still on screen ... Commenting on these lines is fine .. but surely then you do not cancel the subscription when the widget leaves the screen:
} |
I am doing a big project with state_rebuilder, I think he is the best state manager to use. |
@gustavobrian thanks for your compliments and I am happy that you find states_rebuilder useful.
That makes me very happy and I am ready to help you at any time you want.
Can you explain more what you mean by 'with name that the subscriber loses'. (By the way, I didn't understand your last comment (Who is 661/5000). Right now, I am thinking about the possibility of injecting the same model more than once: I have three choices:
|
Hi GIfatahTH, Service: import 'dart:async';
import 'package:meta/meta.dart';
import './../domain/param_test.dart';
import './../domain/test.dart';
import './i_api_test.dart';
class TestStreamService {
IApiTest _api;
TestStreamService({@required IApiTest api}) {
_api = api;
}
////////////////////////////////////////// subscription
StreamSubscription _subscription;
final _streamController = StreamController<Test>();
Stream<Test> get testStream => _streamController.stream;
////////////////////////////////////////// get
void get({@required String id}) {
try {
final ParamTest _paramTest = ParamTest(id: id);
_subscription?.cancel();
_subscription = _api.getTestStream(_paramTest).listen((data) {
_streamController.add(data);
}, onError: (error) {
print('ERROR TestStreamService get listen ==> ' + error?.toString() ?? 'none');
_streamController.addError(error?.message);
});
} catch (error) {
print('ERROR TestStreamService get catch ==> ' + error?.toString() ?? 'none');
_streamController.addError(error?.message);
}
}
////////////////////////////////////////// dispose
void dispose() {
_subscription?.cancel();
_streamController?.close();
}
}
Work fine update screen import 'package:flutter/material.dart';
import 'package:states_rebuilder/states_rebuilder.dart';
import './../../../core/exceptions/error_handler.dart';
import './../../../core/widgets/loading.dart';
import './../data/api_test.dart';
import './../domain/param_test.dart';
import './../domain/test.dart';
import './../service/test_stream_service.dart';
import './test_edit_page.dart';
import './test_view_widget.dart';
class TestStream extends StatelessWidget {
final String id;
TestStream({Key key, @required this.id})
: assert(id != null),
super(key: Key('TestStreamKey' + id));
@override
Widget build(BuildContext context) {
//final User userAuth=Injector.get<UserAuthService>(name: 'UserAuthService').user;
Injector.enableTestMode = true;
return Injector(
disposeModels: true,
inject: [
// Inject(() => ApiTest(), name: id + '_ApiTest'),
/// Inject(() => TestStreamService(api: Injector.get<ApiTest>()), name: id + '_ServiceTest'),
/// Inject.stream(() => Injector.get<TestStreamService>(name: id + '_ServiceTest').testStream, name: id + '_TestStreamService'),
Inject(() => TestStreamService(api: Injector.get<ApiTest>())),
Inject.stream(() => Injector.get<TestStreamService>().testStream),
],
builder: (context) {
//final TestStreamService model = Injector.get<TestStreamService>(name: id + '_ServiceTest');
///final ReactiveModel<Test> modelReactive = Injector.getAsReactive<Test>(name: id + '_TestStreamService');
final TestStreamService model = Injector.get<TestStreamService>();
final ReactiveModel<Test> modelReactive = Injector.getAsReactive<Test>();
//StatesRebuilderDebug.printInjectedModel();
void onTap({Test test}) {
Navigator.pushNamed(context, TestEditPage.name, arguments: ParamTest(test: modelReactive.snapshot.data));
} //onTap
return StateBuilder<Test>(
models: [modelReactive],
initState: (_, __) {
model.get(id: this.id);
}, //initState
builder: (_, _model) {
return Container(
width: MediaQuery.of(context).size.width,
height: 144.0,
// height: 60.0,
child: _model.hasData
? TestView(
test: _model.snapshot.data,
onTap: onTap,
) //TestView
: _model.hasError
? ErrorHandler.showErrorContainer(
context: context,
error: _model.error,
vertical: false,
onTap: () {
model.get(id: this.id);
},
) //ErrorHandler
: const Loading(),
); //Contain
}, //builder
); //StateBuilder
}, //builder
); //Injector
} //build
} //class
But I need to add name in the injector to have several instances: children: <Widget>[
TestStream(id: '5a394230-5509-11ea-fea0-5995a1f05b36'),
TestStream(id: '17b5f9d0-5509-11ea-9753-55060f4a3aa1'),
TestStream(id: '3c53e860-5509-11ea-cdd7-33ae823cfb5d'), But when I do this, listen only the first time and then no longer refresh the screen. This service if you add to the stream, but the view does not perform the setState, StateBuilder does not react, it apparently performs an internal subscriber dispose import 'package:flutter/material.dart';
import 'package:states_rebuilder/states_rebuilder.dart';
import './../../../core/exceptions/error_handler.dart';
import './../../../core/widgets/loading.dart';
import './../data/api_test.dart';
import './../domain/param_test.dart';
import './../domain/test.dart';
import './../service/test_stream_service.dart';
import './test_edit_page.dart';
import './test_view_widget.dart';
class TestStream extends StatelessWidget {
final String id;
TestStream({Key key, @required this.id})
: assert(id != null),
super(key: Key('TestStreamKey' + id));
@override
Widget build(BuildContext context) {
//final User userAuth=Injector.get<UserAuthService>(name: 'UserAuthService').user;
Injector.enableTestMode = true;
return Injector(
disposeModels: true,
inject: [
// Inject(() => ApiTest(), name: id + '_ApiTest'),
Inject(() => TestStreamService(api: Injector.get<ApiTest>()), name: id + '_ServiceTest'),
Inject.stream(() => Injector.get<TestStreamService>(name: id + '_ServiceTest').testStream, name: id + '_TestStreamService'),
//Inject(() => TestStreamService(api: Injector.get<ApiTest>())),
// Inject.stream(() => Injector.get<TestStreamService>().testStream),
],
builder: (context) {
final TestStreamService model = Injector.get<TestStreamService>(name: id + '_ServiceTest');
final ReactiveModel<Test> modelReactive = Injector.getAsReactive<Test>(name: id + '_TestStreamService');
//final TestStreamService model = Injector.get<TestStreamService>();
//final ReactiveModel<Test> modelReactive = Injector.getAsReactive<Test>();
//StatesRebuilderDebug.printInjectedModel();
void onTap({Test test}) {
Navigator.pushNamed(context, TestEditPage.name, arguments: ParamTest(test: modelReactive.snapshot.data));
} //onTap
return StateBuilder<Test>(
models: [modelReactive],
initState: (_, __) {
model.get(id: this.id);
}, //initState
builder: (_, _model) {
return Container(
width: MediaQuery.of(context).size.width,
height: 144.0,
// height: 60.0,
child: _model.hasData
? TestView(
test: _model.snapshot.data,
onTap: onTap,
) //TestView
: _model.hasError
? ErrorHandler.showErrorContainer(
context: context,
error: _model.error,
vertical: false,
onTap: () {
model.get(id: this.id);
},
) //ErrorHandler
: const Loading(),
); //Contain
}, //builder
); //StateBuilder
}, //builder
); //Injector
} //build
} //class
Thanks!!! |
I can also assure you that I am not crazy. |
I tried to replicate your case by faking some dependencies. Please confirm that the code below reproduces your case and confirm if it works. copy the code in the main.dart fine and run in debug, build or release mode. import 'dart:async';
import 'package:flutter/material.dart';
import 'package:states_rebuilder/states_rebuilder.dart';
// The IApiTest and its implementation
abstract class IApiTest {
Stream<Test> getTestStream(ParamTest paramTest);
}
class ApiTest implements IApiTest {
@override
Stream<Test> getTestStream(ParamTest paramTest) {
//Emits a fake stream of integers.
return Stream.periodic(
Duration(seconds: 1),
(val) => Test(
val * int.parse(paramTest.id),
),
);
}
}
// Fake test Class
class Test {
//I used int counter for the sake of illustration
final int counter;
Test(this.counter);
}
class ParamTest {
final String id;
ParamTest({this.id});
}
//TestStreamService
class TestStreamService {
IApiTest _api;
TestStreamService({@required IApiTest api}) {
_api = api;
}
////////////////////////////////////////// subscription
StreamSubscription _subscription;
final _streamController = StreamController<Test>();
Stream<Test> get testStream => _streamController.stream;
////////////////////////////////////////// get
void get({@required String id}) {
try {
final ParamTest _paramTest = ParamTest(id: id);
_subscription?.cancel();
_subscription = _api.getTestStream(_paramTest).listen((Test data) {
_streamController.add(data);
}, onError: (error) {
print('ERROR TestStreamService get listen ==> ' + error?.toString() ??
'none');
_streamController.addError(error?.message);
});
} catch (error) {
print('ERROR TestStreamService get catch ==> ' + error?.toString() ??
'none');
_streamController.addError(error?.message);
}
}
////////////////////////////////////////// dispose
void dispose() {
_subscription?.cancel();
_streamController?.close();
}
}
//TestStream widget
class TestStream extends StatelessWidget {
final String id;
TestStream({Key key, @required this.id})
: assert(id != null),
super(key: Key('TestStreamKey' + id));
@override
Widget build(BuildContext context) {
return Injector(
disposeModels: true,
inject: [
Inject(() => ApiTest(), name: id + "ApiTest"),
Inject(
() => TestStreamService(
api: Injector.get<ApiTest>(name: id + "ApiTest")),
name: id + 'TestStreamService'),
Inject.stream(
() =>
Injector.get<TestStreamService>(name: id + 'TestStreamService')
.testStream,
name: id + 'TestStream'),
],
builder: (context) {
final TestStreamService model =
Injector.get<TestStreamService>(name: id + 'TestStreamService');
final ReactiveModel<Test> modelReactive =
Injector.getAsReactive<Test>(name: id + 'TestStream');
void onTap({Test test}) {
// Navigator.pushNamed(context, TestEditPage.name, arguments: ParamTest(test: modelReactive.snapshot.data));
print(test);
} //onTap
return StateBuilder<Test>(
models: [modelReactive],
initState: (_, __) {
model.get(id: this.id);
}, //initState
builder: (_, _model) {
return Container(
width: MediaQuery.of(context).size.width,
height: 144.0,
// height: 60.0,
child: _model.hasData
? MaterialButton(
child: Text('stream $id ' +
_model.snapshot.data.counter.toString()),
onPressed: onTap,
) //TestView
: _model.hasError
? Text('Error') //ErrorHandler
: Center(child: const CircularProgressIndicator()),
); //Contain
}, //builder
); //StateBuilder
}, //builder
); //Injector
} //build
}
////////////////////////////////////////////////
///
//The main method
void main() => runApp(
MaterialApp(
home: MyApp(),
),
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
//Three TestStreams
TestStream(id: "1"),
TestStream(id: "2"),
TestStream(id: "3"),
],
);
}
} |
I refactor the code this way which looks batter. import 'dart:async';
import 'package:flutter/material.dart';
import 'package:states_rebuilder/states_rebuilder.dart';
// The IApiTest and its implementation
abstract class IApiTest {
Stream<Test> getTestStream(ParamTest paramTest);
}
class ApiTest implements IApiTest {
@override
Stream<Test> getTestStream(ParamTest paramTest) {
//Emits a fake stream of integers.
return Stream.periodic(
Duration(seconds: 1),
(val) => Test(
val * int.parse(paramTest.id),
),
);
}
}
// Fake test Class
class Test {
//I used int counter for the sake of illustration
final int counter;
Test(this.counter);
}
class ParamTest {
final String id;
ParamTest({this.id});
}
//TestStreamService
class TestStreamService {
IApiTest _api;
TestStreamService({@required IApiTest api}) {
_api = api;
}
Stream<Test> get({@required String id}) {
final ParamTest _paramTest = ParamTest(id: id);
return _api.getTestStream(_paramTest);
}
}
//TestStream widget
class TestStream extends StatelessWidget {
final String id;
TestStream({Key key, @required this.id})
: assert(id != null),
super(key: Key('TestStreamKey' + id));
@override
Widget build(BuildContext context) {
return Injector(
inject: [
Inject.stream(
() => Injector.get<TestStreamService>().get(id: id),
name: id + 'TestStream',
),
],
builder: (context) {
final modelReactive =
Injector.getAsReactive<Test>(name: id + 'TestStream');
void onTap({Test test}) {
// Navigator.pushNamed(context, TestEditPage.name, arguments: ParamTest(test: modelReactive.snapshot.data));
print(test);
}
return Container(
width: MediaQuery.of(context).size.width,
height: 144.0,
child: WhenRebuilder(
models: [modelReactive],
onIdle: () => Text('Stream is not called'),
onWaiting: () => Center(child: const CircularProgressIndicator()),
onError: (error) => Text('Error'),
onData: (data) => MaterialButton(
child: Text('stream $id ' + data.counter.toString()),
onPressed: onTap,
),
),
);
},
);
}
}
////////////////////////////////////////////////
///
//The main method
void main() => runApp(
MaterialApp(
home: MyApp(),
),
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Injector(
inject: [
//It is better to register with interface
Inject<IApiTest>(() => ApiTest()),
Inject(
() => TestStreamService(api: Injector.get()),
),
],
builder: (context) {
return Column(
children: <Widget>[
//Three TestStreams
TestStream(id: "1"),
TestStream(id: "2"),
TestStream(id: "3"),
],
);
},
);
}
} |
Great, I'm uploading a video with the first version ... |
the same happens in v2, with the difference that the service stream stops broadcasting. class ApiTest implements IApiTest {
@override
Stream<Test> getTestStream(ParamTest paramTest) {
//Emits a fake stream of integers.
/*
return Stream.periodic(
Duration(seconds: 1),
(val) => Test(
val * int.parse(paramTest.id),
),
);
*/
return Stream.periodic(Duration(seconds: 1), (val) {
int newVal = val * int.parse(paramTest.id);
print(newVal.toString());
return Test(newVal);
});
}
} |
In debug mode it works well in both versions. |
Please go to
and replace with:
and import dart collection. It should work! |
perfect.. import 'package:flutter/foundation.dart';
@override
void didUpdateWidget(StateBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
// if (oldWidget.models != widget.models) {
if (! listEquals(oldWidget.models, widget.models)) {
_dispose(oldWidget);
_initState(widget);
}
if (widget.didUpdateWidget != null)
widget.didUpdateWidget(context, _exposedModel, oldWidget);
} work fine...THANKS and happy code!!! |
@gustavobrian |
Exception: Injecting an already injected model
You are injecting the [ApiMultimedia6c443f00-5420-11ea-cac3-d9bdb02134f5a30798c0-4e6c-11ea-e7a2-3190ade73832] model which is already injected.
This is not allowed.
If we are sure you want to inject it again, try injecting it with custom name.
Hi, I am very excited about this development.
But I do not understand how to insert a new instance if I get the error that it is already injected.
I have a list of items, and I want a separate instance for each one ...
can you guide me
Thanks !!
The text was updated successfully, but these errors were encountered: