question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Null reference used for synchronization with Hilt

See original GitHub issue

Hello, 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:open
  • Created 3 years ago
  • Comments:9 (3 by maintainers)

github_iconTop GitHub Comments

8reactions
danysantiagocommented, Jun 15, 2020

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.:

@AndroidEntryPoint(AbstractFragment::class)
MyFragment : Hilt_MyFragment() { }

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.

4reactions
NitroG42commented, Jun 11, 2020

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Null reference used for synchronization (monitor-enter)
Null reference used for synchronization (monitor-enter) · android · kotlin · nullpointerexception · garbage-collection · finalizer.
Read more >
Change DI Library from Koin to Dagger-Hilt - ProAndroidDev
So I tried to use DI library with Koin and Dagger-Hilt easily, but in my opinion, the Dagger-Hilt is still in alpha stage,...
Read more >
Using Hilt with Room DB - ChandraSaiMohan bhupathi
This story describes how to use Hilt dependency injection using Room DB along with Co routines in Kotlin. This project uses MVVM and...
Read more >
Handling Lifecycles with Lifecycle-Aware Components
Keep your UI controllers (activities and fragments) as lean as possible. They should not try to acquire their own data; instead, use a...
Read more >
E/System: Uncaught exception thrown by finalizer ...
E/System: Uncaught exception thrown by finalizer java.lang.NullPointerException: Null reference used for synchronization (monitor-enter) at ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found