jonataslaw / getx

Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
MIT License
10.38k stars 1.62k forks source link

GetxService onInit in an error GetLifeCycle #3155

Open wxqqh opened 3 months ago

wxqqh commented 3 months ago

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

wxqqh commented 3 months ago

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
  });
}
jonataslaw commented 3 months ago

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.