-
-
Notifications
You must be signed in to change notification settings - Fork 153
Stack Overflow error when two classes depend on each other #82
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
Comments
Sorry, this was just me being oblivious to circular dependencies, nothing wrong with the package |
GetIt should give us some details about circular dependency. To solve this problem here a part solution with this example: Some interfaces and implementations: abstract class SuperLetter {}
abstract class A implements SuperLetter {}
class AC implements A {
final B b;
AC({this.b});
@override
String toString() {
if (b == null) {
return "Im an A.";
}
return "Im an A and my B is: $b";
}
}
abstract class B implements SuperLetter {}
class BC implements B {
final C c;
BC({this.c});
@override
String toString() {
if (c == null) {
return "Im a B.";
}
return "Im a B and my C: $c";
}
}
abstract class C implements SuperLetter {}
class CC implements C {
final SuperLetter a;
CC({this.a});
@override
String toString() {
if (a == null) {
return "Im a C.";
}
return "Im a C and my A: $a"; // might never be reached because of circular dependency
}
} A new Exception to throw when circular dependency is detected. class CircularDependencyException implements Exception {
final wantedType;
final calledTypes;
CircularDependencyException({
this.wantedType,
this.calledTypes,
});
@override
String toString() {
String message = '🔺 Circular dependency ';
for (final e in calledTypes) {
message += '${e.toString()}';
if (e == calledTypes.last) {
message += ' -> $wantedType';
} else {
message += ' -> ';
}
}
return message;
}
} To fight against circular dependency: class GetItProtector {
var _wantedType;
Set _calledTypes;
final GetIt getIt;
GetItProtector(this.getIt);
_check<T>() {
if (_wantedType == null) {
_wantedType = T;
_calledTypes = Set()..add(T);
} else {
if (_calledTypes.contains(T)) {
throw CircularDependencyException(wantedType: T, calledTypes: _calledTypes);
}
_calledTypes.add(T);
}
}
T call<T>() {
_check<T>();
final instance = getIt<T>();
if (instance.runtimeType == _wantedType) {
_wantedType = null;
_calledTypes.clear();
}
return instance;
}
} Example with A -> B -> C -> A circular dependency: void main() {
GetIt g = GetIt.instance;
final myGet = GetItProtector(g);
g.registerLazySingleton<A>(() => AC(
b: myGet(),
));
g.registerLazySingleton<B>(() => BC(
c: myGet(),
));
g.registerLazySingleton<C>(() => CC(
a: myGet<A>(),
));
A a = myGet();
print(a);
} Output:
Example with A -> B -> C -> B void main() {
GetIt g = GetIt.instance;
final myGet = GetItProtector(g);
g.registerLazySingleton<A>(() => AC(
b: myGet(),
));
g.registerLazySingleton<B>(() => BC(
c: myGet(),
));
g.registerLazySingleton<C>(() => CC(
a: myGet<B>(),
));
A a = myGet();
print(a);
} Output
In this solution I don't test if type is a real type and not Object or dynamic. |
You are right. We could use an increment to each call and decrement it after an instance is returned from getIt. In _check method if _wantedType is null we init _depth to 0. T call<T>() {
_check<T>();
_depth += 1;
final instance = getIt<T>();
_depth -= 1;
if (_depth == 0) {
_wantedType = null;
_calledTypes.clear();
}
return instance;
} |
Haven't heard many people complaining about this problem. Have to think how this could be avoided inside get_it |
I am also facing same issue and it is a pain. |
I'm going to say the same thing here as I said on pierre-luc's StackOverflow post on this topic. There's not going to be any way that GetIt can automatically detect circular dependencies without one of two things: an interface for types that get registered that the end developer will be responsible with implementing, or reflection. The latter is a non-starter since Flutter doesn't support it, and the former requires that developers A) know about the feature and B) are willing to put in the work to add the boilerplate into every service class they register. And even then, this is not a solution, but merely a polite warning that there is a problem. There is no real solution to circular dependency, which is fine because it is the result of bad coding practices (tight coupling). The best way to "fix" it is to not have it in the first place. With OP's code, for example, the solution is to not use the constructor to pass in the dependency but instead retrieve it through GetIt or another dependency injection method (such as thunks or lazy evaluation) separate from the constructor. Sure it could be nice to get a heads up if you inadvertently create a circular dependency, but pierre's solution is a lot of work for relatively little gain. The normal detection method of getting stack overflow errors is less clear but no less apparent in indicating that a problem exists, and all you would need to do at that point is to step through your program and you will immediately spot the issue. |
@Abion47 thanks for your comment. I think this exactly what I think! |
I probably simplified the issue here: https://stackoverflow.com/questions/71223255/getit-plugin-stack-overflow-error-when-two-classes-depend-on-each-other @escamoteur Can you please comment what the correct solution would be? |
@tomasbaran The correct solution is to not have a circular dependency at all. And to be perfectly honest, two classes that have a tightly-coupled dependency on each other is code smell anyway. If you are running into this issue, it's a good sign that you need to reconsider your architecture. |
I totally agree. I also just added a comment to the SO. |
I have an Auth Service and a firebase service which are both registered as singletons and usually everything works fine. However, when I try and access the Auth service from firebase service and vice versa, eg.
AuthService(FirebaseService()) <-> FirebaseService(AuthService())
, I get a stack overflow error with the full error trace as follows:The modules were registered using injectable, so from this:
It generated this:
I'm fairly sure my code isn't causing the problem as deleting the body of the services, then running leads to the same error. But, if this is meant to happen, is there anyway I can work around it and still access the each service in the other service.
Thanks
The text was updated successfully, but these errors were encountered: