jonataslaw / getx

Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

GetxService onInit in an error GetLifeCycle

wxqqh opened this issue · comments

GetxService onInit in an error GetLifeCycle

The problem is that in GetLifeCycleBase, onInit is a FutureFunctional(), but it is executed synchronously. So the completion status of onInit CANNOT be obtained after Get.put is completed outside

the code is at there GetLifeCycleBase

mixin GetLifeCycleBase {
  // Internal callback that starts the cycle of this controller.
  void _onStart() {
    if (_initialized) return;
    onInit(); // <--- The problem here is that in GetLifeCycleBase, 
    _initialized = true;
  }
}

example:

class DbService extends GetxService {
  Future<DbService> onInit() async { /// onInit is Future<dynamic> Function()
    print('$runtimeType delays 2 sec');
    await 2.delay();
    print('$runtimeType ready!');
    return this;
  }
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

   group('FimberLoggerService', () {
    setUpAll(() async {
      // Get.putAsync(DbService()); 
      /// ⬆️ The signature and usage of this function do not match the documentation https://github.com/jonataslaw/getx/blob/master/README.md#getxservice
      /// The argument type 'DbService' can't be assigned to the parameter type 'Future<dynamic> Function()'.

      /// must to
      Get.put(DbService());
      /// or 
      Get.putAsync(() async => DbService());

    })

    test("Get DbService", () {
      DbService db = Get.find(); // <--- onInit not finish
    })
}

To Reproduce
run the test case

Expected behavior
Get.putAsync may get the onInit finish OR just like doc call onInit MANUAL

Now this code can work, but it is very cumbersome

class DbService extends GetxService {
  Future<DbService> onInit() async {
    if(_initialized) return this;

    print('$runtimeType delays 2 sec');
    await 2.delay();
    print('$runtimeType ready!');
    return this;

    _initialized = true;
  }

  bool _initialized = false;
}

// in test group
  setUpAll(() async {
    await Get.putAsync(() async {
      GetxService service = GetxService();
      await service.onInit();
      return service;
    });
  })

Getx Version:

dependencies:
  get: ^4.6.6

Describe on which device you found the bug:
In test case

this code is work for me but not elegant:

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

abstract class AppService<T extends GetxService> extends GetxService {
  @override
  Future<T> onInit() async {
    if (_initialized) return this as T;

    super.onInit();
    await init();

    _initialized = true;
    return this as T;
  }

  Future<void> init() async => throw UnimplementedError();
  bool _initialized = false;
}

class DBService extends AppService {
  @override
  Future<void> init() async {
    /// do sth. only called once
  }
}

void main() {
  setUpAll(() async {
    await Get.putAsync(() => DBService().onInit());
    DBService db = Get.find();
    /// do sth. with DBService instance
  });
}

Thanks for opening this issue, and you're absolutely right!

This is an architectural problem that I didn't think about at the time I created putAsync, to be honest, I used exactly the code you sent (I believe you've seen something like this on discord) for many years.
About a year ago I created a Wrapper that allows me to do this in a more elegant way, I believe I can insert it into the package.