Skip to content
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

Closed
gustavobrian opened this issue Feb 20, 2020 · 24 comments
Closed

hi, cannot inject new instance... #52

gustavobrian opened this issue Feb 20, 2020 · 24 comments

Comments

@gustavobrian
Copy link

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 !!

@GIfatahTH
Copy link
Owner

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 Injector.get<Foo>() or Injector.getAsReactive<Foo>(). The type (Foo) will be used to get the model and as they are all registered with the same type the states_rebuilder does not know exactly which model to return. For this reason, injecting and injected model is not allowed.

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.

@D10100111001
Copy link

@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],

@GIfatahTH
Copy link
Owner

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.

This is a good idea 👍

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.

Your case looks different from @gustavobrian 's.
You said that the new registration will override the old one, won't it?

BUT, if one overrides a model, no one can get the old registered instance.

@gustavobrian
Copy link
Author

Thanks!!! GIfatahTH

my code:
class MultimediaStreamPrincipal extends StatelessWidget {
final String id;
final bool circular;
final double width;
MultimediaStreamPrincipal({Key key, @required this.id, this.circular = true, this.width}) : assert(id != null);
// super(key: Key('MultimediaStreamPrincipal' + id));

@OverRide
Widget build(BuildContext context) {
//final User userAuth=Injector.get(name: 'UserAuthService').user;
return Injector(
disposeModels: true,
inject: [
Inject(() => ApiMultimedia(), name: id + 'ApiMultimedia'),
Inject(() => MultimediaPrincipalStreamService(api: Injector.get(name: id + 'ApiMultimedia')), name: id + 'MultimediaPrincipalStreamService'),
Inject.stream(() => Injector.get(name: id + 'MultimediaPrincipalStreamService').multimediaStream, name: id + 'MultimediaPrincipalStreamServiceStream'),
],
builder: (context) {
final MultimediaPrincipalStreamService model = Injector.get(name: id + 'MultimediaPrincipalStreamService');
final ReactiveModel modelReactive = Injector.getAsReactive(name: id + 'MultimediaPrincipalStreamServiceStream');
//StatesRebuilderDebug.printInjectedModel();

    return StateBuilder<Multimedia>(
      models: [modelReactive],
      initState: (_, __) {
        model.getPrincipal(id: this.id);
      }, //initState
      onRebuildState: (_, state) {
        // print('onRebuildState');
      },
      builder: (_, _model) {
        return ShowImage(
          multimedia: _model?.snapshot?.data,
          small: true,
          circular: circular,
          width: width,
        );
      }, //builder
    ); //StateBuilder
  }, //builder
); //Injector

} //build
} //class

when the widget repeat in the list:
Exception: Injecting an already injected model
You are injecting the [a30798c0-4e6c-11ea-e7a2-3190ade73832ApiMultimedia] 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.

If you are testing the widget set [Injector.enableTestMode] to true

It 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 ...
how can i solve it ...

Thanks again for your work!

@gustavobrian
Copy link
Author

If I add enabletestMode = true the error disappears, but I wait for your comment to know if something I am doing wrong.
My peculiarity is that I need to instantiate the same widget with the same id more than once on the screen.

Widget build(BuildContext context) {
//final User userAuth=Injector.get(name: 'UserAuthService').user;
Injector.enableTestMode = true;
return Injector(
disposeModels: true,
inject: [
Inject(() => ApiMultimedia(), name: id + 'ApiMultimedia'),
Inject(() => MultimediaPrincipalStreamService(api: Injector.get(name: id + 'ApiMultimedia')), name: id + 'MultimediaPrincipalStreamService'),
Inject.stream(() => Injector.get(name: id + 'MultimediaPrincipalStreamService').multimediaStream, name: id + 'MultimediaPrincipalStreamServiceStream'),
],
builder: (context) {
final MultimediaPrincipalStreamService model = Injector.get(name: id + 'MultimediaPrincipalStreamService');
final ReactiveModel modelReactive = Injector.getAsReactive(name: id + 'MultimediaPrincipalStreamServiceStream');.............

@GIfatahTH
Copy link
Owner

I do not know what exactly you are doing and why you are doing it this way. BUT as you said :

My peculiarity is that I need to instantiate the same widget with the same id more than once on the screen.

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.

If I add enabletestMode = true the error disappears,

The error disappears fro sure but is your code still work as you intended.

What exactly Injector.enabletestMode does is to ignore any registration with the same name. That means for example for ApiMultimedia once registered for the first time, any further registration is ignored and hence there is only one instance of ApiMultimedia.

If your code works when setting Injector.enabletestMode to true, please consider removing Injector.enabletestMode and injecting ApiMultimedia, MultimediaPrincipalStreamService, and multimediaStream without a name in the widget parent of MultimediaStreamPrincipal.

@D10100111001
Copy link

D10100111001 commented Feb 22, 2020

@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 Injector like:

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 get getAsReactive methods are used, the last type that was registered will be returned (this is how it currently works if the multiple injection was allowed).

@gustavobrian
Copy link
Author

gustavobrian commented Feb 22, 2020

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.
The only difference is that one has a name on the injector and the other does not. I am with the latest flutter master version
NOT WORK:

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(name: 'UserAuthService').user;
// Injector.enableTestMode = true;
return Injector(
disposeModels: true,
inject: [
// Inject(() => ApiTest(), name: id + '_ApiTest'),
Inject(() => TestStreamService(api: Injector.get()), name: id + '_ServiceTest'),
Inject.stream(() => Injector.get(name: id + '_ServiceTest').testStream, name: id + '_TestStreamService'),
//Inject(() => TestStreamService(api: Injector.get())),
// Inject.stream(() => Injector.get().testStream),
],
builder: (context) {
final TestStreamService model = Injector.get(name: id + '_ServiceTest');
final ReactiveModel modelReactive = Injector.getAsReactive(name: id + '_TestStreamService');
//final TestStreamService model = Injector.get();
//final ReactiveModel modelReactive = Injector.getAsReactive();
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


WORK:

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(name: 'UserAuthService').user;
// Injector.enableTestMode = true;
return Injector(
disposeModels: true,
inject: [
// Inject(() => ApiTest(), name: id + '_ApiTest'),
// Inject(() => TestStreamService(api: Injector.get()), name: id + '_ServiceTest'),
// Inject.stream(() => Injector.get(name: id + '_ServiceTest').testStream, name: id + '_TestStreamService'),
Inject(() => TestStreamService(api: Injector.get())),
Inject.stream(() => Injector.get().testStream),
],
builder: (context) {
//final TestStreamService model = Injector.get(name: id + '_ServiceTest');
//final ReactiveModel modelReactive = Injector.getAsReactive(name: id + '_TestStreamService');
final TestStreamService model = Injector.get();
final ReactiveModel modelReactive = Injector.getAsReactive();
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

@GIfatahTH
Copy link
Owner

@D10100111001

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 Injector like:
List allInstances = Injector.getAll();
List<ReactiveModel> allReactiveInstances = Injector.getAllAsReactive();
If the option for opting in is true, and the user has registered multiple of the same type, and the regular get getAsReactive methods are used, the last type that was registered will be returned (this is how it currently works if the multiple injection was allowed).

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?

@gustavobrian
Copy link
Author

661/5000
Yes, thanks for your reply.
But I think there is a problem, I am seeing that the screen does not refresh because it runs on
reactive_model at the end of the file

void _unsubscribe () {
if (_subscription! = null) {
print ('===> 5.reactiveModel._unsubscribe');
// _subscription.cancel ();
// _subscription = null;
}
}

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:
void _unsubscribe () {

// _subscription.cancel ();
// _subscription = null;

}

@gustavobrian
Copy link
Author

I am doing a big project with state_rebuilder, I think he is the best state manager to use.
Very simple and very complete, I developed with Provider, Redux and Bloc, and I saw some mbox.
I'm trying to test almost all the functionality described in the documentation and it's going great.
My only obstacle so far is with the injection of stream with name that the subscriber loses. I think I'm doing things right, but I don't know if there is something that I don't interpret properly or there really is an error there.
Thanks for your work !!

@GIfatahTH
Copy link
Owner

@gustavobrian thanks for your compliments and I am happy that you find states_rebuilder useful.

I am doing a big project with state_rebuilder

That makes me very happy and I am ready to help you at any time you want.

My only obstacle so far is with the injection of stream with name that the subscriber loses

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:

  1. prevent injecting the model more than one time, because I think there is no need to inject the same model (service) twice.
  2. Allow for multiple injections of the same model but the last injection will override the former so that at any time there is only one instance of the model.
  3. Allow for multiple injections without restriction, which may lead to bugs because, Injector can not know which instance to return.

@gustavobrian
Copy link
Author

Hi GIfatahTH,
I will try to explain myself better with my code

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!!!

@gustavobrian
Copy link
Author

I can also assure you that I am not crazy.
Try both the stable channel and the master.
In the build or run the screen does not react and does not update.
Ohhhhh In debug mode if it works correctly ...
In exchange I make a flutter clean to make sure there is nothing strange left ...
Now I need help to understand what happens !!!

@GIfatahTH
Copy link
Owner

@gustavobrian

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"),
     ],
   );
 }
}

@GIfatahTH
Copy link
Owner

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"),
          ],
        );
      },
    );
  }
}

@gustavobrian
Copy link
Author

Great, I'm uploading a video with the first version ...
In play mode the same problem happens, in debug mode it works fine.
  I finish and try the second version.

@gustavobrian
Copy link
Author

@gustavobrian
Copy link
Author

the same happens in v2, with the difference that the service stream stops broadcasting.
As in v1 add the line in the service to print the value on the console.
Now I upload the video.

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

@gustavobrian
Copy link
Author

@gustavobrian
Copy link
Author

In debug mode it works well in both versions.
I tested it on the master and stable channel.
Also in two different teams.
thanks for your dedication

@GIfatahTH
Copy link
Owner

Please go to state_builder.dart file and on line 364 remove

if (oldWidget.models != widget.models) {

and replace with:

if (!listEquals(oldWidget.models, widget.models)) {

and import dart collection.

It should work!

@gustavobrian
Copy link
Author

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!!!
Gustavo!

@GIfatahTH
Copy link
Owner

@gustavobrian
Thanks for raising this issue. I learned from it a lot. The issue will be fixed in the next release.
Happy coding you too. And I hope to hear your final thoughts about states_rebuilder once you finish your project which I hope to see.

@GIfatahTH GIfatahTH mentioned this issue Feb 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants