ConstraintLayout 没有为在 RecyclerView 上单击的项目设置 NavController [英] ConstraintLayout does not have a NavController set for item clicked on RecyclerView

查看:25
本文介绍了ConstraintLayout 没有为在 RecyclerView 上单击的项目设置 NavController的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我点击一个项目时,有时它会工作,有时它会崩溃.

When I click on an item, sometimes it works and sometimes it crashes.

以下是点击项目后的致命异常.

Below is the FATAL EXCEPTION after clicking on an item.

2020-03-31 21:59:18.087 15383-15383/com.aliton.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.aliton.myapp, PID: 15383
    java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{b3bb13b V.E...C.. .......D 0,336-720,518} does not have a NavController set
        at androidx.navigation.Navigation.findNavController(Navigation.java:84)
        at com.aliton.myapp.Adapter.StoreSelectAdapter$StoreSelectViewHolder$1.onChanged(StoreSelectAdapter.java:147)
        at com.aliton.myapp.Adapter.StoreSelectAdapter$StoreSelectViewHolder$1.onChanged(StoreSelectAdapter.java:142)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6351)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:896)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:786)

在这种情况下,首先我需要将一个项目 (StoreEntity) 插入到我的房间数据库中,监听它的 id,然后导航到具有定义的对象的其他片段方向.

In this case, first I need to insert an item (StoreEntity) into my room database, listen to its id and then navigate to other fragment with objects defined in direction.

下面是我的适配器:

public class StoreSelectAdapter extends RecyclerView.Adapter<StoreSelectAdapter.StoreSelectViewHolder> {

    private static final String TAG = "debinf SummaryAdapter";

    private FragmentActivity fragmentActivity;

    public StoreSelectAdapter(FragmentActivity fragmentActivity) {
        this.fragmentActivity = fragmentActivity;
    }

    private static final DiffUtil.ItemCallback<StoreModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<StoreModel>() {
        @Override
        public boolean areItemsTheSame(@NonNull StoreModel oldItem, @NonNull StoreModel newItem) {
            return oldItem.getKey().equals(newItem.getKey());
        }

        @Override
        public boolean areContentsTheSame(@NonNull StoreModel oldItem, @NonNull StoreModel newItem) {
            return oldItem.getKey().equals(newItem.getKey()) && oldItem.getName().equals(newItem.getName()) && oldItem.getAddress().equals(newItem.getAddress());
        }
    };

    private AsyncListDiffer<StoreModel> differ = new AsyncListDiffer<StoreModel>(this, DIFF_CALLBACK);

    @NonNull
    @Override
    public StoreSelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_select_store, parent, false);
        return new StoreSelectViewHolder(view, fragmentActivity);
    }

    @Override
    public void onBindViewHolder(@NonNull StoreSelectViewHolder holder, int position) {
        StoreModel store = differ.getCurrentList().get(position);
        Log.i(TAG, "onBindViewHolder: "+store.getName());

        holder.storeName.setText(store.getName());
        holder.storeAddress.setText(store.getAddress());

        if (store.getImage() != null && !store.getImage().isEmpty()) {
            Picasso.get().load(store.getImage()).networkPolicy(NetworkPolicy.OFFLINE).into(holder.storeImage, new Callback() {
                @Override
                public void onSuccess() {

                }

                @Override
                public void onError(Exception e) {
                    Picasso.get().load(store.getImage()).into(holder.storeImage);
                }
            });
        } else {
            holder.storeImage.setImageResource(android.R.drawable.stat_sys_phone_call_on_hold);
        }

        /*holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick: onBindViewHolder");
                Toast.makeText(context, "onClick: onBindViewHolder", Toast.LENGTH_SHORT).show();
                AddstoreFragmentDirections.ActionAddstoreFragmentToBarcodeFragment direction = AddstoreFragmentDirections.actionAddstoreFragmentToBarcodeFragment(store);
                Navigation.createNavigateOnClickListener()
            }
        });*/
    }

    @Override
    public int getItemCount() {
        return differ.getCurrentList().size();
    }

    public void submitList(List<StoreModel> stores) {
        differ.submitList(stores);
    }

    public List<StoreModel> getCurrentItemList() {
        return differ.getCurrentList();
    }

    public class StoreSelectViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        //private static final String TAG = "StoreSelectViewHolder";

        public ImageView storeImage;
        public TextView storeName, storeAddress;

        private FragmentActivity fragmentActivity;
        private LocalDatabaseViewModel localDatabaseViewModel;

        public StoreSelectViewHolder(@NonNull View itemView, FragmentActivity fragmentActivity) {
            super(itemView);

            storeImage = (ImageView) itemView.findViewById(R.id.item_store_image);
            storeName = (TextView) itemView.findViewById(R.id.item_store_name);
            storeAddress = (TextView) itemView.findViewById(R.id.item_store_address);

            this.fragmentActivity = fragmentActivity;

            itemView.setOnClickListener(this);

        }

        @Override
        public void onClick(View v) {
            StoreModel store = differ.getCurrentList().get(getAdapterPosition());
            Log.i(TAG, "onClick: storeName selected: "+store.getName());
            Log.i(TAG, "onClick: storeKey selected: "+store.getKey());
            StoreEntity storeEntity = new StoreEntity(store.getName(), store.getImage());
            storeEntity.setKey(store.getKey());
            localDatabaseViewModel = ViewModelProviders.of(fragmentActivity).get(LocalDatabaseViewModel.class);
            localDatabaseViewModel.getStoreIdFromInsertedItem().observe(fragmentActivity, new Observer<Long>() {
                @Override
                public void onChanged(Long itemId) {
                    Log.i(TAG, "onChanged: itemId in adapter is "+itemId);
                    SelectstoreFragmentDirections.ActionSelectstoreFragmentToBarcodeFragment direction = SelectstoreFragmentDirections.actionSelectstoreFragmentToBarcodeFragment(store, itemId); 
                    Navigation.findNavController(v).navigate(direction); // this is the StoreSelectAdapter.java:147
                }
            });
            localDatabaseViewModel.insertStore(storeEntity);


        }

    }
}

感谢您为解决此问题提供的任何帮助.

I appreciate any help to solve this issue.

编辑

下面是我的 activity_main.xml 我的片段 NavHost 被声明的地方.

Below is my activity_main.xml where my fragment NavHost is declared.

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/main_drawer"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <fragment
            android:id="@+id/main_fragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/main_bottomnav"
            app:defaultNavHost="true"
            app:navGraph="@navigation/mainnav_graph"/>

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/main_bottomnav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:menu="@menu/main_navmenu"
            android:background="@color/colorAccent"
            app:itemIconTint="@drawable/botton_item_color"
            app:itemTextColor="@drawable/botton_item_color">

        </com.google.android.material.bottomnavigation.BottomNavigationView>

    </androidx.constraintlayout.widget.ConstraintLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/main_sidebar"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:menu="@menu/main_sidebarmenu"/>

</androidx.drawerlayout.widget.DrawerLayout>

下面是我的 mainnav_graph.xml

...
<fragment
    android:id="@+id/barcodeFragment"
    android:name="com.aliton.myapp.BarcodeFragment"
    android:label="fragment_barcode"
    tools:layout="@layout/fragment_barcode" >

    <argument
        android:name="store"
        app:argType="com.aliton.myapp.Model.StoreModel" />
    <action
        android:id="@+id/action_barcodeFragment_to_productdetailFragment"
        app:destination="@id/productdetailFragment" />
    <argument
        android:name="storeId"
        app:argType="long" />
</fragment>
<fragment
    android:id="@+id/selectstoreFragment"
    android:name="com.aliton.myapp.SelectstoreFragment"
    android:label="fragment_selectstore"
    tools:layout="@layout/fragment_selectstore" >
    <action
        android:id="@+id/action_selectstoreFragment_to_addstoreFragment"
        app:destination="@id/addstoreFragment" />
    <action
        android:id="@+id/action_selectstoreFragment_to_barcodeFragment"
        app:destination="@id/barcodeFragment" />
</fragment>
...

下面是我的 fragment_selectstore.xml 作为 SelectstoreFragment.java 的内容.

And below is my fragment_selectstore.xml as the content of SelectstoreFragment.java.

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SelectstoreFragment">

    <androidx.cardview.widget.CardView
        android:id="@+id/selectstore_cardview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        app:cardCornerRadius="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <TextView
            android:id="@+id/selectstore_question"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Are you at any of the store shown below?"
            android:textSize="28sp"
            android:gravity="center"
            android:textStyle="bold"/>


    </androidx.cardview.widget.CardView>

    <FrameLayout
        android:id="@+id/selectstore_framelayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/selectstore_cardview">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/selectstore_recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical">

        </androidx.recyclerview.widget.RecyclerView>

        <TextView
            android:id="@+id/selectstore_nodata"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="No store found!"
            android:textSize="22sp" />

    </FrameLayout>

    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
        android:id="@+id/selectstore_fab"
        style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="8dp"
        android:contentDescription="Outra loja desc"
        android:text="Outra loja"
        app:icon="@drawable/common_full_open_on_phone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

更新

1) 我还实现了一个 interface 用于 AdapterFragment 之间的通信以获取 viewonViewCreated() 中,但应用程序不断崩溃.

1) I've also implemented an interface for communication between Adapter and Fragment to get the view in the onViewCreated(), but the app keeps crashing.

2) 我还尝试使用以下命令在片段布局中定义我的 NavHostFragment 的 navController

2) I've also tried to get the navController of my NavHostFragment defined in the fragment layout, using the following command

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    Log.i(TAG, "onViewCreated: ");

    // https://stackoverflow.com/a/53902696/4300670
    // NOT WORKING
    adapter = new StoreSelectAdapter(getActivity(), new StoreSelectAdapter.OnStoreSelectListener() {
        @Override
        public void onStoreSelected(StoreModel storeModel, Long itemId) {
            SelectstoreFragmentDirections.ActionSelectstoreFragmentToBarcodeFragment direction = SelectstoreFragmentDirections.actionSelectstoreFragmentToBarcodeFragment(storeModel, itemId);
            Navigation.findNavController(getActivity(), R.id.main_fragment).navigate(direction);
        }
    });
    storeRecyclerview.setAdapter(adapter);
}

但现在我得到 NullPointerException

2020-04-02 17:11:22.163 6015-6015/com.aliton.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.aliton.myapp, PID: 6015
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.app.Activity.requireViewById(int)' on a null object reference
        at androidx.core.app.ActivityCompat.requireViewById(ActivityCompat.java:363)
        at androidx.navigation.Navigation.findNavController(Navigation.java:58)
        at com.aliton.myapp.SelectstoreFragment$4.onStoreSelected(SelectstoreFragment.java:221)
        at com.aliton.myapp.Adapter.StoreSelectAdapter$StoreSelectViewHolder$1.onChanged(StoreSelectAdapter.java:149)
        at com.aliton.myapp.Adapter.StoreSelectAdapter$StoreSelectViewHolder$1.onChanged(StoreSelectAdapter.java:145)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

3) 我复制了 Navigation 类中的方法,以了解导致错误的原因,它们是:

3) I've copied the methods inside Navigation class in order to understand what is causing the error and here they are:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    Log.i(TAG, "onViewCreated: ");

    adapter = new StoreSelectAdapter(getActivity(), new StoreSelectAdapter.OnStoreSelectListener() {
        @Override
        public void onStoreSelected(StoreModel storeModel, Long itemId) {
            SelectstoreFragmentDirections.ActionSelectstoreFragmentToBarcodeFragment direction = SelectstoreFragmentDirections.actionSelectstoreFragmentToBarcodeFragment(storeModel, itemId);
            NavController navController = myFindViewNavController(view);
            if (navController == null) {
                Log.i(TAG, "onStoreSelected: ISSUE NavController set");
            }                

        }
    });

    storeRecyclerview.setAdapter(adapter);
}

private NavController myFindViewNavController(View view) {
    while (view != null) {
        Log.i(TAG, "myFindViewNavController: view != null - "+view);
        NavController controller = myGetViewNavController(view);
        if (controller != null) {
            Log.i(TAG, "myFindViewNavController: controller != null");
            return controller;
        }
        ViewParent parent = view.getParent();
        view = parent instanceof View ? (View) parent : null;
        Log.i(TAG, "myFindViewNavController: view.getParent() - "+view);
    }
    return null;
}

private NavController myGetViewNavController(View view) {
    Object tag = view.getTag(R.id.nav_controller_view_tag);
    Log.i(TAG, "myGetViewNavController: tag is "+tag);
    NavController controller = null;
    if (tag instanceof WeakReference) {
        Log.i(TAG, "myGetViewNavController: tag instanceof WeakReference");
        controller = ((WeakReference<NavController>) tag).get();
    } else if (tag instanceof NavController) {
        Log.i(TAG, "myGetViewNavController: tag instanceof NavController");
        controller = (NavController) tag;
    }
    return controller;
}

验证logcat,我的adapter中的ViewModel似乎在导航到下一个fragment后又被触发了:

Verifying the logcat, it seems that the ViewModel in my adapter is been triggered once again after navigating to the next fragment:

debinf SummaryAdapter: onClick: storeName selected: Posto Shell Convem
debinf SummaryAdapter: onClick: storeKey selected: M0Ysk5jRIdtiJ9zyUhXG
debinf SummaryAdapter: onChanged: itemId in adapter is 40, view id: -1
debinf SelStoreFrag: myFindViewNavController: view != null - androidx.constraintlayout.widget.ConstraintLayout{fb9835d V.E...... ........ 0,0-1080,1437}
debinf SelStoreFrag: myGetViewNavController: tag is null
debinf SelStoreFrag: myFindViewNavController: view.getParent() - android.widget.FrameLayout{4af5206 V.E...... ........ 0,0-1080,1437 #7f090137 app:id/main_fragment}
debinf SelStoreFrag: myFindViewNavController: view != null - android.widget.FrameLayout{4af5206 V.E...... ........ 0,0-1080,1437 #7f090137 app:id/main_fragment}
debinf SelStoreFrag: myGetViewNavController: tag is androidx.navigation.NavController@47f0c7
debinf SelStoreFrag: myGetViewNavController: tag instanceof NavController
debinf SelStoreFrag: myFindViewNavController: controller != null
debinf BarcodeFrag: onCreateView: 
debinf BarcodeFrag: onCreateView: fragArgs is com.aliton.myapp.Model.StoreModel@77fbfcb
debinf BarcodeFrag: onPermissionGranted: 
debinf BarcodeFrag: startCamera: 
debinf ExtFun: isFlashSupported: false
debinf ExtFun: startCameraForAllDevices: 
debinf BarcodeFrag: onResume:
debinf SummaryAdapter: onChanged: itemId in adapter is 41, view id: -1
debinf SelStoreFrag: myFindViewNavController: view != null - androidx.constraintlayout.widget.ConstraintLayout{fb9835d V.E...... .......D 0,0-1080,1437}
debinf SelStoreFrag: myGetViewNavController: tag is null
debinf SelStoreFrag: myFindViewNavController: view.getParent() - null
debinf SelStoreFrag: onStoreSelected: ISSUE NavController set

似乎解决方案将通过 adapter 中的 ViewModel 正确解决插入项 id 的侦听.

It seems that the solution will come by properly solving the listening of the inserted item id via ViewModel in the adapter.

推荐答案

IllegalStateExceptionNullPointerException 不再发生,因为我已经删除了 ViewModel 来自 adapter 并将其放在 fragment 中.

The IllegalStateException or NullPointerException is no longer occurring, since I've removed the ViewModel from the adapter and placed it in the fragment.

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    Log.i(TAG, "onViewCreated: ");

    localDatabaseViewModel = ViewModelProviders.of(getActivity()).get(LocalDatabaseViewModel.class);
    adapter = new StoreSelectAdapter(new StoreSelectAdapter.OnStoreSelectListener() {
        @Override
        public void onStoreSelected(StoreModel storeModel) {
            StoreEntity storeEntity = new StoreEntity(storeModel.getName(), storeModel.getImage());
            storeEntity.setKey(storeModel.getKey());
            localDatabaseViewModel.getStoreIdFromInsertedItem().observe(getViewLifecycleOwner(), new Observer<Long>() {
                @Override
                public void onChanged(Long itemId) {
                    SelectstoreFragmentDirections.ActionSelectstoreFragmentToBarcodeFragment direction = SelectstoreFragmentDirections.actionSelectstoreFragmentToBarcodeFragment(storeModel, itemId);
                    Navigation.findNavController(view).navigate(direction);
                }
            });
            localDatabaseViewModel.insertStore(storeEntity);

        }
    });

    storeRecyclerview.setAdapter(adapter);
}

并停止监听房间数据库中插入项目的id.

And stop listening to the id of the inserted item in the room database.

@Override
public void onStop() {
    Log.i(TAG, "onStop: ");
    if (localDatabaseViewModel != null) {
        localDatabaseViewModel.getStoreIdFromInsertedItem().removeObservers(getViewLifecycleOwner());
    }
    super.onStop();
}

这篇关于ConstraintLayout 没有为在 RecyclerView 上单击的项目设置 NavController的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆