Dagger Hilt: Custom Entry Point for FragmentFactory Integration

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

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

查看所有标签

猜你喜欢:

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

《裂变:秒懂人工智能的基础课》

《裂变:秒懂人工智能的基础课》

王天一 / 电子工业出版社·博文视点 / 2018-6-13 / 59.00元

人工智能是指通过普通计算机程序实现的人类智能技术,这一学科不仅具有非凡的科学意义,对人类自身生存方式的影响也在不断加深。本书作为人工智能领域的入门读物,内容围绕人工智能的核心框架展开,具体包括数学基础知识、机器学习算法、人工神经网络原理、深度学习方法与实例、深度学习之外的人工智能和实践应用场景等模块。本书力图为人工智能初学者提供关于这一领域的全面认识,也为进一步的深入研究建立坚实的基础。一起来看看 《《裂变:秒懂人工智能的基础课》》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

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

UNIX 时间戳转换