问题
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)
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
.
Below is my adapter:
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.
EDIT
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>
Below is my 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>
...
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>
UPDATE
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) 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);
}
But now I get the 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) 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;
}
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
It seems that the solution will come by properly solving the listening of the inserted item id
via ViewModel
in the adapter
.
回答1:
Before you move the interface implementation to the fragment.
You are accessing the NavController
from the wrong place or with the wrong access.
You have to move this action to the fragment hosting the recycler view. Then you can navigate to your desired destination.
findNavController().navigate(R.id.your_destination)
After you move the interface implementation to the fragment.
You are accessing NavController
by the wrong accessor you just have to call the findNavController()
method only.
for the summary, you can initialize your adapter
adapter = new StoreSelectAdapter(getActivity(), new StoreSelectAdapter.OnStoreSelectListener() {
@Override
public void onStoreSelected(StoreModel storeModel, Long itemId) {
SelectstoreFragmentDirections.ActionSelectstoreFragmentToBarcodeFragment direction = SelectstoreFragmentDirections.actionSelectstoreFragmentToBarcodeFragment(storeModel, itemId);
findNavController().navigate(direction);
}
});
The findNavController()
function/method is an extension function for the fragment you will find it under the package androidx.navigation.fragment
Hope you get what I am trying to illustrate.
回答2:
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);
}
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();
}
来源:https://stackoverflow.com/questions/60962539/constraintlayout-does-not-have-a-navcontroller-set-for-item-clicked-on-recyclerv