Dagger Hilt: Custom Entry Point for FragmentFactory Integration

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

内容简介: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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

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

智能时代

智能时代

吴军 / 中信出版集团 / 2016-8 / 68.00

大数据和机器智能的出现,对我们的技术发展、商业和社会都会产生重大的影响。作者吴军在《智能时代:大数据与智能革命重新定义未来》中指出,首先,我们在过去认为非常难以解决的问题,会因为大数据和机器智能的使用而迎刃而解,比如解决癌症个性化治疗的难题。同时,大数据和机器智能还会彻底改变未来的商业模式,很多传统的行业都将采用智能技术实现升级换代,同时改变原有的商业模式。大数据和机器智能对于未来社会的影响是全方......一起来看看 《智能时代》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具