Dagger Hilt: Custom Entry Point for FragmentFactory Integration

栏目: IT技术 · 发布时间: 5年前

内容简介:Inmy previous article I explored Dagger Hilt, which is a new library that wraps aroud Dagger 2 dependency injection framework. As part of my research for that post, I migrated IDoCare app from “vanilla” Dagger to Dagger Hilt. It didn’t take much time and I

Inmy previous article I explored Dagger Hilt, which is a new library that wraps aroud Dagger 2 dependency injection framework. As part of my research for that post, I migrated IDoCare app from “vanilla” Dagger to Dagger Hilt. It didn’t take much time and I was quite satisfied with the results.

Unfortunately, I introduced a serious bug during the migration. Fortunately, one of the readers immediately spotted that bug and alerted me (shoutout to Guilherme). Not surprisingly, the bug related to Android lifecycles and process death. However, I couldn’t figure out how to fix it right away due to the way Hilt operates.

In this post, I’ll describe that bug, explain why it’s tricky to fix when using Hilt, and then show you how to use Hilt’s custom Entry Points to work around its own limitations.

The Bug

Before I migrated IDoCare to Dagger Hilt, onCreate() method in its MainActivity looked like this:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        getControllerComponent().inject(this);
        mFragmentManager.setFragmentFactory(mFragmentFactory);
        super.onCreate(savedInstanceState);

        ...

        if (savedInstanceState == null) {
            mScreensNavigator.toAllRequests();
        }
    }

After migration to Dagger Hilt, it took on the following shape:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mFragmentManager.setFragmentFactory(mFragmentFactory);

        ...

        if (savedInstanceState == null) {
            mScreensNavigator.toAllRequests();
        }
    }

I had to delay setFragmentFactory(FragmentFactory) call because Hilt injects dependencies during a call to super.onCreate() , so I wouldn’t have a valid reference to FragmentFactory before this point. However, this seemingly innocent change introduced a critical bug into my code.

During “normal” operation, the app worked alright. However, when I started IDoCare, put it in the background, and then made the system kill its process, the app crashed on the next startup.

The root cause of this bug is realated to the mechanism of app’s state restoration after process death . When the system restores an application after its process had been killed, it re-initializes the latest attached Fragment during a call to super.onCreate() in MainActivity. And since I no longer bind my FragmentFactory to FragmentManager before this call, the sytem can’t use it to re-create the Fragment, which is a fatal error.

The Challenge

Once I identified the root cause of the bug, it should be very easy to fix it, right? Well, not exactly.

The additional challenge in this case is “lifecycle mismatch”: I need to set my custom FragmentFactory before the call to super.onCreate() , but, when I use Hilt, this object is injected into MainActivity only during that same call. I can’t really use the object before I inject it, right?

Looks like it’s impossible to resolve this bug using Hilt’s “default” approach. Fortunately, Hilt includes additional convention called Entry Point which can save the day in this case.

Bug Fix Using Custom Entry Point

Long story short, this code fixes the bug:

@EntryPoint
    @InstallIn(ActivityComponent.class)
    public interface MainActivityEntryPoint {
        public FragmentManager getFragmentManager();
        public FragmentFactory getFragmentFactory();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        MainActivityEntryPoint entryPoint =
                EntryPointAccessors.fromActivity(this, MainActivityEntryPoint.class);
        entryPoint.getFragmentManager().setFragmentFactory(entryPoint.getFragmentFactory());

        super.onCreate(savedInstanceState);

        ...

        if (savedInstanceState == null) {
            mScreensNavigator.toAllRequests();
        }
    }

Let’s understand what’s going on here.

In onCreate() method, I bind a custom FragmentFactory to FragmentManager before the call to super.onCreate() . That’s the fix. To make this work, I get references to both FragmentManager and FragmentFactory from an object of type MainActivityEntryPoint.

MainActivityEntryPoint is an interface that I myself defined. This interface is annotated with @EntryPoint annotation to let Dagger Hilt know that it should generate the respective implementation. The additional @InstallIn annotation declares the “ownership” of this entry point. In this case, since MainActivityEntryPoint is installed in ActivityComponent, that entry point will give me access to ActivityComponent’s objects graph. The two methods inside my custom entry point indicate that I’ll use it to grab instances of FragmentManager and FragmentFactory.

A call to EntryPointAccessors.fromActivity(Activity, Class) is a classical service location. Since I declared that MainActivityEntryPoint will be installed in ActivityComponent, all my Activities will be able to retrieve that entry point using this approach. This means that the name of this entry point is only important for readability and maintainability. In addition, you can make your entry points protected if you’d like to restrict their usage (but not private ).

Intuitively, you can think of entry points as “windows” into object graphs of specific Hilt’s Components.

Developers who took my Dagger 2 or Android Architecture courses can also think of entry points as interfaces that composition roots implement. In fact, that’s exactly what’s going on behind all Hilt’s magic. These interfaces allow you to interact with various composition roots directly (without “injection by annotation magic”), while limiting the amount of “visible” services. In essense, entry points leverage Interface Segregation Principle (I in SOLID) to provide access to a subset of composition roots’ methods.

In most cases, you wouldn’t grab dependencies from Components using entry points and rely on @Inject annotated fileds instead. However, in some cases, you might want to just get an instance of a specific object. That’s what entry points are used for.

Summary

In this short post you learned how to use custom Entry Points, which are advanced feature in Dagger Hilt, to work around lifecycle mismatch between Hilt and FragmentFactory.

On the one hand, it’s a bit disappointing that you need to jump through additional hoops to integrate Dagger Hilt with FragmentFactory. On the other hand, the fact that Hilt already supported a convention which allowed to implement relatively simple workaround is encouraging.

As usual, thanks for reading and leave your comments and questions below.

If you liked this post, then you'll surely like my courses


以上所述就是小编给大家介绍的《Dagger Hilt: Custom Entry Point for FragmentFactory Integration》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机程序设计艺术・卷3

计算机程序设计艺术・卷3

[美] 高德纳(Donald E. Knuth) / 贾洪峰 / 人民邮电出版社 / 2017-2 / 198.00元

《计算机程序设计艺术》系列被公认为计算机科学领域的权威之作,深入阐述了程序设计理论,对计算机领域的发展有着极为深远的影响。本书为该系列的第3卷,全面讲述了排序和查找算法。书中扩展了卷1中数据结构的处理方法,并对各种算法的效率进行了大量的分析。一起来看看 《计算机程序设计艺术・卷3》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具