Home > Enterprise >  GetIt plugin Stack Overflow Error when two classes depend on each other
GetIt plugin Stack Overflow Error when two classes depend on each other

Time:02-24

I get the Stack Overflow error when I have the following design:

enter image description here

I get that the problem is the first dotted arrow.

Here it is in the code:

  • timer_container.dart
import 'package:flutter/material.dart';
import 'package:minimalist_timer_app/widgets/timer_container/timer_container_controller.dart';
import 'package:minimalist_timer_app/services/service_locator.dart';

class TimerContainer extends StatefulWidget {
  const TimerContainer({Key? key}) : super(key: key);

  @override
  State<TimerContainer> createState() => _TimerContainerState();
}

class _TimerContainerState extends State<TimerContainer> {
  @override
  void initState() {
    super.initState();
    _widgetController.init();
  }

  final TimerContainerController _widgetController = getIt<TimerContainerController>();
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<String>(
        valueListenable: _widgetController,
        builder: (_, timer, __) {
          return Text(
            timer,
            style: const TextStyle(fontSize: 70, fontWeight: FontWeight.w700),
          );
        });
  }
}
  • timer_container_controller.dart
import 'package:flutter/material.dart';
import 'package:minimalist_timer_app/services/service_locator.dart';
import 'package:minimalist_timer_app/services/timer_service.dart';

class TimerContainerController extends ValueNotifier<String> {
  TimerContainerController() : super(defaultTimerString);
  final _timerService = getIt<TimerService>();

  init() => _timerService.init();
}
  • timer_service.dart
import 'package:flutter/material.dart';
import 'package:minimalist_timer_app/services/service_locator.dart';
import 'package:minimalist_timer_app/widgets/timer_container/timer_container_controller.dart';

class TimerService {
  final _timerNotifier = getIt<TimerContainerController>();

  init() {
    // ...
    _timerNotifier.value = _newString;
  }
}

I also found a workaround: If I don't declare _timerNotifier or _timerService in one of the files, the issue is gone, then I have to change my code like so:

  • _timerNotifier.value -> getIt<TimerContainerController>().value
  • _timerService.init() -> getIt<TimerService>().init()

So, in other words both cannot be declared at the same time since they target each other.

The question is, what is the best practice here? Is my workaround the way to go (not declaring the variable)? Or is my design wrong? If so, how shall I change it?

CodePudding user response:

Best practice is to not have circular dependencies. There is no good clean way to do what you are doing, so the best thing to do is to redesign your architecture to not have this problem in the first place.

If for whatever reason you need to do something like this, move the getIt call to the method where you actually need the service instead of trying to cache the value in a field initializer or constructor (which is defeating the purpose of get_it anyway):

  • timer_container_controller.dart
class TimerContainerController extends ValueNotifier<String> {
  TimerContainerController() : super(defaultTimerString);

  init() {
    final _timerService = getIt<TimerService>();
    _timerService.init();
  }
}
  • timer_service.dart
class TimerService {
  init() {
    // ...

    final _timerNotifier = getIt<TimerContainerController>();
    _timerNotifier.value = _newString;
  }
}

But again, and I cannot stress this enough, the fact that you have to do this is an indicator of code smell, and you'd be better off using a different approach entirely in which two separate classes don't depend on each other for their initialization.

CodePudding user response:

Big shot out to @Abion47. Thanks to his direction, time, dedication and care I was able to get to the answer.

Before getting to the solution: why is the design presented in the question not good?

  • imminent reason: the stack overflow issue that is caused by circular dependency
  • code-smell: avoid tight coupling
  • be in accordance with both single-responsibility and the dependency inversion principle (the "S" and "D" of SOLID, respectively): data layer should not be including logic; data layer shall be worried only about its data and should not even know there are services that change its data, etc.

The answer is: divide the controller layer into:

  • controller layer: whose only job is to manage/control state of the timer_container.dart UI. In other words, this layer does NOT worry about the data, nor about the logic — only about the delegation of logic. As @Abion47 pointed out, this layer is not even a service — it's a controller/manager of state. It just calls the appropriate services from the service layer (timer_service.dart)
  • data layer: whose only job is notify about the value's change

like so:

enter image description here

Here is the code:

  • timer_container.dart
import 'package:flutter/material.dart';
import 'package:minimalist_timer_app/widgets/timer_container/timer_container_controller.dart';
import 'package:minimalist_timer_app/services/service_locator.dart';

class TimerContainer extends StatefulWidget {
  const TimerContainer({Key? key}) : super(key: key);

  @override
  State<TimerContainer> createState() => _TimerContainerState();
}

class _TimerContainerState extends State<TimerContainer> {
  final TimerContainerController _widgetController = getIt<TimerContainerController>();

  @override
  void initState() {
    super.initState();
    _widgetController.init();
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<String>(
        valueListenable: _widgetController.timerNotifier,
        builder: (_, timer, __) {
          return Text(
            timer,
            style: const TextStyle(fontSize: 70, fontWeight: FontWeight.w700),
          );
        });
  }
}
  • timer_container_controller.dart
import 'package:minimalist_timer_app/services/service_locator.dart';
import 'package:minimalist_timer_app/services/timer_service.dart';
import 'package:minimalist_timer_app/widgets/timer_container/timer_container_notifier.dart';

class TimerContainerController {
  final timerNotifier = getIt<TimerContainerNotifier>();
  final _timerService = getIt<TimerService>();

  init() async => await _timerService.init();
}
  • timer_container_notifier.dart
import 'package:flutter/material.dart';
import 'package:minimalist_timer_app/utils/constants.dart';

class TimerContainerNotifier extends ValueNotifier<String> {
  TimerContainerNotifier() : super(mkDefaultTimerString);
}
  • timer_service.dart
import 'package:flutter/material.dart';
import 'package:minimalist_timer_app/services/service_locator.dart';
import 'package:minimalist_timer_app/widgets/timer_container/timer_container_controller.dart';

class TimerService {
  final _timerNotifier = getIt<TimerContainerNotifier>();

  init() {
    // ...
    _timerNotifier.value = _newString;
  }
}
  • Related