Skip to content

Unable to get instance when using registerSingletonAsync and signal ready. #244

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

Closed
jraldrin opened this issue Jan 18, 2022 · 5 comments
Closed

Comments

@jraldrin
Copy link

jraldrin commented Jan 18, 2022

Hi,

I'm confused on how to get instance when using registerSingletonAsync and signal ready. I've read the docs and looked at the unit tests and I have been unable to get it to work. Note: when looking at the tests in aysnc_test that use registerSingletonAsync none of the tests seemed to try and get instance from getIt.

I'm using getit version 7.20 and Flutter version 2.8.1, Dart version: 2.15.1 (stable), running on windows 10.

Following is a couple of unit tests I created, using types in async_test, that dont pass.

Note: looking at code it appears that the initWithSignal future func is being executed after the "await getIt.allReady()" is executed.

Thanx in advance for any help. John A

import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';

int constructorCounter = 0;
int disposeCounter = 0;
int errorCounter = 0;

abstract class TestBaseClass {}

class TestClassParam {
  final String? param1;
  final int? param2;

  TestClassParam({this.param1, this.param2});
}

class TestClass extends TestBaseClass {
  GetIt? getIt;
  bool initCompleted = false;

  /// if we do the initialisation from inside the constructor the init function has to signal GetIt
  /// that it has finished. For that we need to pass in the completer that we got from the factory call
  /// that we set up in the registration.
  TestClass({required bool internalCompletion, this.getIt}) {
    constructorCounter++;
    if (internalCompletion) {
      assert(getIt != null);
      initWithSignal();
    }
  }

  /// This one signals after a delay
  Future initWithSignal() {
    return Future.delayed(const Duration(milliseconds: 10)).then((_) {
      getIt!.signalReady(this);
      initCompleted = true;
    });
  }

  // We use this as dummy init that will return a future
  Future<TestClass> init() async {
    await Future.delayed(const Duration(milliseconds: 10));
    initCompleted = true;
    return this;
  }

  void dispose() {
    disposeCounter++;
  }
}

main() {
  setUp(() {
    GetIt.I.reset();
  });

  group("registerSingletonAsync, calling signalReady", () {
    test('attempt to get instance with getIt.get', () async {
      final getIt = GetIt.instance;
      getIt.registerSingletonAsync<TestClass>(
        () => Future.delayed(const Duration(milliseconds: 1))
            .then((_) => TestClass(internalCompletion: true, getIt: getIt)),
      );

      await getIt.allReady();

      final instance = getIt<TestClass>();

      expect(instance.initCompleted, isTrue);
    });

    test('attempt to get instance with getAsync', () async {
      final getIt = GetIt.instance;
      getIt.registerSingletonAsync<TestClass>(
        () => Future.delayed(const Duration(milliseconds: 1))
            .then((_) => TestClass(internalCompletion: true, getIt: getIt)),
      );

      await getIt.allReady();

      final instance = await getIt.getAsync<TestClass>();

      expect(instance.initCompleted, isTrue);
    });
  });
}
@escamoteur
Copy link
Collaborator

When you want to signal your ready state manually using signalReady, you have to set signalsReady=true when calling registerSingletonAsync.

Does that solve your problem? In most cases, you don't have to do it manually but use automatic signaling.
See also https://www.youtube.com/watch?v=YJ52kSfSMyM&list=PL-BFoWYMGZ2TvwY0uf1fBJB1358utBlAz&index=10

@jraldrin
Copy link
Author

jraldrin commented Feb 3, 2022

Hi,
Thanx for the response, Also, so sorry I made the signalReady mistake. Much embarrassed. However, when I set signalReady to true

getIt.registerSingletonAsync(
() => Future.delayed(const Duration(milliseconds: 1))
.then((_) => TestClass(internalCompletion: true, getIt: getIt)),
signalsReady: true,
);

I get the following when running the test.

dart:core _AssertionError._throwNew
package:get_it/get_it_impl.dart 372:7 _GetItImplementation._findFactoryByNameAndType
package:get_it/get_it_impl.dart 393:29 _GetItImplementation.get
package:get_it/get_it_impl.dart 430:12 _GetItImplementation.call
test\async_singleton_bug_test.dart 68:29 main..
Bad state: This instance of the type TestClass is not available in GetIt If you have registered it as LazySingleton, are you sure you have used it at least once?
package:get_it/get_it_impl.dart 7:18 throwIf
package:get_it/get_it_impl.dart 1087:5 _GetItImplementation._findFactoryByInstance
package:get_it/get_it_impl.dart 1122:28 _GetItImplementation.signalReady
test\async_singleton_bug_test.dart 35:14 TestClass.initWithSignal.
===== asynchronous gap ===========================
dart:async Future.then
test\async_singleton_bug_test.dart 34:61 TestClass.initWithSignal
test\async_singleton_bug_test.dart 28:7 new TestClass
test\async_singleton_bug_test.dart 62:26 main....
===== asynchronous gap ===========================
dart:async Future.then
test\async_singleton_bug_test.dart 62:14 main...
package:get_it/get_it_impl.dart 937:48 _GetItImplementation._register.
===== asynchronous gap ===========================
dart:async Future.then
package:get_it/get_it_impl.dart 908:23 _GetItImplementation._register
package:get_it/get_it_impl.dart 653:5 _GetItImplementation.registerSingletonAsync
test\async_singleton_bug_test.dart 60:13 main..
test\async_singleton_bug_test.dart 58:52 main..

'package:get_it/get_it_impl.dart': Failed assertion: line 372 pos 7: 'instanceFactory != null': Object/factory with type TestClass is not registered inside GetIt.
(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;
Did you forget to register it?)

@escamoteur
Copy link
Collaborator

escamoteur commented Feb 3, 2022 via email

@jraldrin
Copy link
Author

jraldrin commented Feb 3, 2022

Thanx again for the help. Praying I haven't done something stupid again.
Only change I have made is to add signalReady: true to registerSingletonAsync calls.

`import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';

int constructorCounter = 0;
int disposeCounter = 0;
int errorCounter = 0;

abstract class TestBaseClass {}

class TestClassParam {
  final String? param1;
  final int? param2;

  TestClassParam({this.param1, this.param2});
}

class TestClass extends TestBaseClass {
  GetIt? getIt;
  bool initCompleted = false;

  /// if we do the initialisation from inside the constructor the init function has to signal GetIt
  /// that it has finished. For that we need to pass in the completer that we got from the factory call
  /// that we set up in the registration.
  TestClass({required bool internalCompletion, this.getIt}) {
    constructorCounter++;
    if (internalCompletion) {
      assert(getIt != null);
      initWithSignal();
    }
  }

  /// This one signals after a delay
  Future initWithSignal() {
    return Future.delayed(const Duration(milliseconds: 10)).then((_) {
      getIt!.signalReady(this);
      initCompleted = true;
    });
  }

  // We use this as dummy init that will return a future
  Future<TestClass> init() async {
    await Future.delayed(const Duration(milliseconds: 10));
    initCompleted = true;
    return this;
  }

  void dispose() {
    disposeCounter++;
  }
}

main() {
  setUp(() {
    GetIt.I.reset();
  });

  group("registerSingletonAsync, calling signalReady", () {
    test('attempt to get instance with getIt.get', () async {
      final getIt = GetIt.instance;
      getIt.registerSingletonAsync<TestClass>(
        () => Future.delayed(const Duration(milliseconds: 1))
            .then((_) => TestClass(internalCompletion: true, getIt: getIt)),
          signalsReady: true,
      );

      await getIt.allReady();

      **final instance = getIt<TestClass>();**

      expect(instance.initCompleted, isTrue);
    });

    test('attempt to get instance with getAsync', () async {
      final getIt = GetIt.instance;
      getIt.registerSingletonAsync<TestClass>(
        () => Future.delayed(const Duration(milliseconds: 1))
            .then((_) => TestClass(internalCompletion: true, getIt: getIt)),
        signalsReady: true
      );

      await getIt.allReady();

      **final instance = await getIt.getAsync<TestClass>();**

      expect(instance.initCompleted, isTrue);
    });
  });
}
`


@escamoteur
Copy link
Collaborator

Hi, actually you found a bug. It seems you were the first person who used registerSingletonAsync together with manually Signalling ready. My guess is that you probably won't need that combination but I would need to understand more what you are doing.

anyway it's fixed in the next version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants