1. 程式人生 > >Android MVVM DataBinding在Fragment onCreateView()中getAdapter()顯示空指標的問題的一種解決方法

Android MVVM DataBinding在Fragment onCreateView()中getAdapter()顯示空指標的問題的一種解決方法

Android MVVM框架和Data Binding庫已經出來很長一段時間了,但是自己最近才開始在專案中使用,很是”慚愧”。因為自己習慣了Android原來的那種所有邏輯都放在Fragment或Activity中處理的方式,所以在接觸Data Binding的時候,難免會遇到一些比較奇怪的問題,例如自己這次遇見的問題:在onCreateView()中ViewPager的getAdapter()報了空指標這樣的錯誤。因為這個問題對於我自己來說比較具有代表性,所以特意在此記錄一下。
在改寫為MVVM之前,原來的邏輯是不會有getAdapter()為空這樣的bug的(其中getAdapter()在setViewPager函式中使用到了),如下:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (hotCategoryBoardBinding.hotCategoryViewPager.getAdapter() == null) {
    hotCategoryBoardBinding.hotCategoryViewPager.setAdapter(new HotCategoryPagerAdapter(generateLines(inflater, hotCategoryBoardBinding.hotCategoryViewPager
, isFull))); } CirclePageIndicator pageIndicator = hotCategoryBoardBinding.hotCategoryIndicator; pageIndicator.setViewPager(hotCategoryBoardBinding.hotCategoryViewPager); pageIndicator.setOnPageChangeListener(this);

因為需要使用到DataBinding,主要是把setAdapter程式碼修改為註解繫結的方式,如下:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
hotCategoryViewModel = new HotCategoryViewModel();
hotCategoryBoardBinding = HotCategoryBoardBinding.inflate(inflater, container, false);       
hotCategoryBoardBinding.setViewModel(hotCategoryViewModel);

...
//省略了部分不太重要的程式碼 hotCategoryViewModel.setViews(generateLines(inflater, hotCategoryBoardBinding.hotCategoryViewPager, isFull));//替代了setAdapter CirclePageIndicator pageIndicator = hotCategoryBoardBinding.hotCategoryIndicator; pageIndicator.setViewPager(hotCategoryBoardBinding.hotCategoryViewPager);//當程式設計師執行到了這一行的時候,產生崩潰 pageIndicator.setOnPageChangeListener(this);

靜態函式如下:

@BindingAdapter("setHotCategoryViewPager")
public static void setHotCategoryViewPager(HotCategoryViewPager hotCategoryViewPager, List<View> lists){
    if(hotCategoryViewPager.getAdapter() == null) {
        hotCategoryViewPager.setAdapter(new HotCategoryPagerAdapter(lists));
    }
}

xml中的相應程式碼:

 <com.telenav.arp.module.onebox.category.HotCategoryViewPager
     android:id="@+id/hotCategoryViewPager"
     android:layout_width="0dp"
     android:layout_height="fill_parent"
     android:layout_weight="1"
     auto:setHotCategoryViewPager="@{viewModel.views}"//在xml中設定adapter
     android:layoutDirection="locale"
     android:gravity="center_vertical" />

上面三個程式碼塊基本貼出了修改之後的主幹部分,但這個時候發生了崩潰,堆疊如下:

06-19 07:23:35.799 16043-16043/com.telenav.app.denali.na E/AndroidRuntime: FATAL EXCEPTION: main                                                                    Process: com.telenav.app.denali.na, PID: 16043
                                                                          java.lang.IllegalStateException: ViewPager does not have adapter instance.
                                                                              at com.viewpagerindicator.CirclePageIndicator.setViewPager(CirclePageIndicator.java:388)
                                                                              at com.telenav.arp.module.onebox.category.HotCategoryFragment.createPagedView(HotCategoryFragment.java:151)
                                                                              at com.telenav.arp.module.onebox.category.HotCategoryFragment.onCreateView(HotCategoryFragment.java:217)
                                                                              at android.app.Fragment.performCreateView(Fragment.java:2220)
                                                                              at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:973)
                                                                              at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
                                                                              at android.app.BackStackRecord.run(BackStackRecord.java:793)
                                                                              at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1535)
                                                                              at android.app.FragmentManagerImpl$1.run(FragmentManager.java:482)
                                                                              at android.os.Handler.handleCallback(Handler.java:739)
                                                                              at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                              at android.os.Looper.loop(Looper.java:148)
                                                                              at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                              at java.lang.reflect.Method.invoke(Native Method)
                                                                              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

這個時候我感到很困惑,對於我來說,我只是把setAdapter由直接設定改為了通過繫結(當然,註解繫結的時候setAdapter不一定像之前一樣在同一個地方同樣地時間點被呼叫),為什麼會出現這個bug呢?

這個時候我加了斷點,分別在三個地方添加了斷點:

@Override
public void onResume() {
    super.onResume();//第一個地方,標註為1
}
// page indicator
CirclePageIndicator pageIndicator = hotCategoryBoardBinding.hotCategoryIndicator; 
pageIndicator.setViewPager(hotCategoryBoardBinding.hotCategoryViewPager);//第二個地方,在onCreateView中,標註為2
pageIndicator.setOnPageChangeListener(this);
@BindingAdapter("setHotCategoryViewPager")
public static void setHotCategoryViewPager(HotCategoryViewPager hotCategoryViewPager, List<View> lists){
    if(hotCategoryViewPager.getAdapter() == null) {
        hotCategoryViewPager.setAdapter(new HotCategoryPagerAdapter(lists));//第三個地方,真正設定adapter的地方,標註為3
    }
}

通過斷點(其實通過崩潰能感覺出來),程式執行的順序是2、1、3。
也就是說當我執行到pageIndicator.setViewPager的時候,3還沒有執行,所以會報出之前的錯誤,我曾經考慮過把pageIndicator.setViewPager的程式碼放置到onResume中,但是onResume的邏輯也會在@BindingAdapter(“setHotCategoryViewPager”)之前執行,所以這裡應該是需要知曉這種databinding設定adapter的方式是在何時執行的,或者怎麼樣能讓這個設定adapter的操作提前在pageIndicator.setViewPager之前執行。

通過查閱資料和相應的API,我發現這一行程式碼:hotCategoryBoardBinding.executePendingBindings();

完整程式碼如下:

hotCategoryViewModel.setViews(generateLines(inflater, hotCategoryBoardBinding.hotCategoryViewPager, isFull));
hotCategoryBoardBinding.executePendingBindings();//同樣放置在onCreateView中,但是在`pageIndicator.setViewPager`之前

這個時候再去執行,發現崩潰消失了。

那麼executePendingBindings是什麼意思呢:它的解釋如下:

/**
     * Evaluates the pending bindings, updating any Views that have expressions bound to
     * modified variables. This <b>must</b> be run on the UI thread.
     */

大概的意思就是:這個操作必須在UI執行緒執行;評估待辦的繫結,對一些繫結到已改變的變數的View進行重新整理。相當於強制實時更新我們的UI。所以我在這裡執行了executePendingBindings之後,相當於把setAdapter操作提前在了pageIndicator.setViewPager之前,所以這個bug就被解決了。

因為我對MVVM的DataBinding可能理解還不夠深入,所以採用了這種方式去解決我的問題,如果看到文章並且解決過相似問題的朋友有更好的解決方案,麻煩告知我一下,謝謝~