Null reference used for synchronization with Hilt
See original GitHub issueHello, I was happy trying Hilt unfortunatelly I have an issue on a new project I began 2 weeks ago. Simple project with activity/fragment/viewmodel navigation etc…
However I get a crash on the first fragment I display, I first tough it came from the viewmodel but if I inject any variable the crash occurs. If I remove any thing that would require injection on the fragment, no crash:
2020-06-11 17:38:00.980 22646-22646/com.myapp.debug E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.myapp.debug, PID: 22646
java.lang.NullPointerException: Null reference used for synchronization (monitor-enter)
at com.myapp.ui.login.Hilt_LoginFragment.componentManager(Hilt_LoginFragment.java:88)
at com.myapp.ui.login.Hilt_LoginFragment.generatedComponent(Hilt_LoginFragment.java:79)
at com.myapp.ui.login.Hilt_LoginFragment.inject(Hilt_LoginFragment.java:98)
at com.myapp.ui.login.Hilt_LoginFragment.initializeComponentContext(Hilt_LoginFragment.java:62)
at com.myapp.ui.login.Hilt_LoginFragment.onAttach(Hilt_LoginFragment.java:54)
at androidx.fragment.app.Fragment.onAttach(Fragment.java:1758)
at com.myapp.ui.login.Hilt_LoginFragment.onAttach(Hilt_LoginFragment.java:44)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2853)
Here is the generated fragment and just after the crashing line:
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.FragmentGenerator")
public abstract class Hilt_LoginFragment extends AbstractFragment implements GeneratedComponentManager<Object> {
private ContextWrapper componentContext;
private volatile FragmentComponentManager componentManager;
private final Object componentManagerLock = new Object();
Hilt_LoginFragment(@LayoutRes int layoutId) {
super(layoutId);
}
Hilt_LoginFragment() {
super();
}
@Override
@CallSuper
public void onAttach(Context context) {
super.onAttach(context);
initializeComponentContext();
}
@Override
@CallSuper
@MainThread
public void onAttach(Activity activity) {
super.onAttach(activity);
Preconditions.checkState(componentContext == null || FragmentComponentManager.findActivity(componentContext) == activity, "onAttach called multiple times with different Context! Hilt Fragments should not be retained.");
initializeComponentContext();
}
private void initializeComponentContext() {
// Only inject on the first call to onAttach.
if (componentContext == null) {
// Note: The LayoutInflater provided by this componentContext may be different from super Fragment's because we getting it from base context instead of cloning from the super Fragment's LayoutInflater.
componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this);
inject();
}
}
@Override
public Context getContext() {
return componentContext;
}
@Override
public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
LayoutInflater inflater = super.onGetLayoutInflater(savedInstanceState);
return LayoutInflater.from(FragmentComponentManager.createContextWrapper(inflater, this));
}
@Override
public final Object generatedComponent() {
return componentManager().generatedComponent();
}
protected FragmentComponentManager createComponentManager() {
return new FragmentComponentManager(this);
}
protected final FragmentComponentManager componentManager() {
if (componentManager == null) {
synchronized (componentManagerLock) {
if (componentManager == null) {
componentManager = createComponentManager();
}
}
}
return componentManager;
}
protected void inject() {
((LoginFragment_GeneratedInjector) generatedComponent()).injectLoginFragment(UnsafeCasts.<LoginFragment>unsafeCast(this));
}
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
ViewModelProvider.Factory factory = DefaultViewModelFactories.getFragmentFactory(this);
if (factory != null) {
return factory;
}
return super.getDefaultViewModelProviderFactory();
}
}
protected final FragmentComponentManager componentManager() {
if (componentManager == null) {
synchronized (componentManagerLock) { //componentManagerLock IS NULL
if (componentManager == null) {
componentManager = createComponentManager();
}
}
}
return componentManager;
}
I tried with the debugger and the componentManagerLock IS null when we try to synchronise! Which makes no sense to me… I suppose it can be a bytecode transformation issue, so I tried with android gradle plugin 4.0.0 and 4.1.0-beta01/gradle 6.5 but same issue. I’m on windows 10 x64 and running the project on an emulator.
I tried on a sample project but the issue doesn’t occurs, and I can’t share code of my current project (it’s for a client). If I can provide any help tell me, I’m too tired right now but I’ll work on what triggers this/sample later.
Issue Analytics
- State:
- Created 3 years ago
- Comments:9 (3 by maintainers)
Top GitHub Comments
The issues occurs when the immediate superclass of an
@AndroidEntryPoint
annotated class has a constructor with default parameters.The root problem is that the generated
Hilt_
class is in Java and the mechanism for which methods with default values are called is lost during the bytecode transformation, causing the unexpected behaviour.You can workaround by using the ‘long-form’ of
@AndroidEntryPoint
, i.e.:Which will follow the Java->Kotlin interop path and get the expected results. Additionally you can use
@JvmOverloads
in case you have multiple default params.The solution we have in mind so far is a bit complicated because we might need to rely on the synthetic method bitmask and
kotlin/jvm/internal/DefaultConstructorMarker
interpretation, which is a bit scary. In the meantime we can detect this scenario and throw a more clear error with the workaround.I found the issue, and I just didn’t put the right line on my first try of a sample (I’m sad I lost 2 hours). The issue comes from my AbstractFragment having this constructor:
abstract class AbstractFragment(@LayoutRes layoutId: Int = 0) : Fragment(layoutId)
Using
abstract class AbstractFragment: Fragment()
or
abstract class AbstractFragment: Fragment(0)
(This one was what I tried in my sample… I should have pasted the whole line!!!)
Make the issue appears instantly. Bonus: I now have an easy fix as we were not using this argument anyway. I’ll post the sample repo in an edit in a few seconds.