1

I have a curious bug in my android app. I have a home fragment with a recyclerview adapter implementing an interface for click listener: on physical devices (not emulator) it works only after restarting the app or go to some other fragments and then returning to home fragment: after that, it works. The listener is implemented in other recycler view adapters in my app and works fine otherwise. I've tried every combination of clickable and focusable true/false in the CardViews which rappresent an item of my list, but nothing works. Thanks for help! (I have no errors while running the app)

EDIT I found the problem: the first time the app is launched, I ask locations permissions with the requestManifestPermissions() method shown in MainActivity: after the user accept or decline it, the OnClick in the adapter end to work. Any advice?

Adapter code:

public class HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private String[] names = {
        "Ristoranti",
        "Bar",
        "Hotel",
        "Coffee",
        "Cinema",
        "Gelaterie",
        "Barbieri",
        "Pizzerie",
        "Banche",
        "Parking",
        "Ospedali",
        "Musei",
        "Supermercati",
        "Birrerie",
        "Farmacie"
};

private int[] images = {
        R.drawable.food,
        R.drawable.bar,
        R.drawable.hotel,
        R.drawable.coffee,
        R.drawable.cinema,
        R.drawable.icecream,
        R.drawable.barber,
        R.drawable.pizza,
        R.drawable.bank,
        R.drawable.parking,
        R.drawable.hospital,
        R.drawable.museum,
        R.drawable.supermarket,
        R.drawable.beer,
        R.drawable.pharma
};

private OnItemClickListener onClickListener;

public HomeAdapter() {

}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = 
    LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_home, 
    parent, false);
    return new MenuHolder(view, onClickListener);
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    MenuHolder viewHolder = (MenuHolder) holder;
    viewHolder.displayView(names[position], images[position]);
}

@Override
public int getItemCount() {
    return names.length;
}

public void setOnClickListener(OnItemClickListener listener) {
    onClickListener = listener;
}

public String getItem(int position) {
    return names[position];
}

HomeFragment (which uses the adapter) code:

public class HomeFragment extends Fragment {

private static final int REQUEST_FINE_LOCATION = 1;
private boolean isLocationEnabled;

private RecyclerView recyclerView;

public HomeFragment() {

}

@Nullable
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    View view = inflater.inflate(R.layout.fragment_recyclerview, container, false);
    isLocationEnabled = checkPermissions();
    recyclerView = view.findViewById(R.id.recyclerview_fav);
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(requireContext());
    recyclerView.setLayoutManager(linearLayoutManager);
    recyclerView.setHasFixedSize(true);
    return view;
}

@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    // Adapter
    HomeAdapter homeAdapter = new HomeAdapter();
    recyclerView.setAdapter(homeAdapter);
    homeAdapter.setOnClickListener((v, position) -> {
        if (isLocationEnabled) {
            NavController controller = Navigation.findNavController(requireActivity(), navHo
            Bundle bundle = new Bundle();
            bundle.putString("type", homeAdapter.getItem(position));
            bundle.putBoolean("nearby", true);
            controller.navigate(R.id.nearbyFragment, bundle);
        } else {
            requestPermissions();
        }
    });
}

MainActivity (I use single activity architecture)

public class MainActivity extends AppCompatActivity {

private DrawerLayout drawerLayout;
private NavigationView navigationView;
private AppBarConfiguration appBarConfiguration;

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar5);
    setSupportActionBar(toolbar);

    drawerLayout = findViewById(R.id.drawer);
    navigationView = findViewById(R.id.navigation_view);

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);

    appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph())
            .setOpenableLayout(drawerLayout)
            .build();
    NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration);

    navigationView.setNavigationItemSelectedListener(menuItem -> {
        int id = menuItem.getItemId();
        if (id == R.id.fav) {
            navController.navigate(R.id.favouritesFragment);
        } else if (id == R.id.search) {
            AlertDialog.Builder builder = 
                new AlertDialog.Builder(MainActivity.this, 
          R.style.MyAlertDialogStyle);
            SpannableString title = new SpannableString("Cerca");
            title.setSpan(new ForegroundColorSpan(
           Color.parseColor("#2565AE")), 0, title.length(), 
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            builder.setTitle(title);
            final EditText input = new EditText(MainActivity.this);
            input.setInputType(InputType.TYPE_CLASS_TEXT);
            builder.setView(input);
            builder.setPositiveButton("OK", (dialog, which) -> {
                Bundle bundle = new Bundle();
                bundle.putString("type", input.getText().toString());
                bundle.putBoolean("nearby", false);
                navController.navigate(R.id.nearbyFragment, bundle);
                Toast.makeText(MainActivity.this, input.getText().toString(),          
            Toast.LENGTH_SHORT).show();
            });
            builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
            builder.show();
        } else if (id == R.id.setting) {
            navController.navigate(R.id.settingsFragment);
        } else if (id == R.id.home) {
            navController.navigate(R.id.homeFragment);
        } else if (id == R.id.bookmarks) {
            navController.navigate(R.id.bookmarksFragment);
        } else if (id == R.id.map) {
            navController.navigate(R.id.mapFragment);
        }
        new Handler().postDelayed(() -> drawerLayout.closeDrawer(navigationView), 200);
        return false;
    });
}

@Override
public boolean onSupportNavigateUp() {
    NavController navController = Navigation.findNavController(this,                    
     R.id.nav_host_fragment);
    return NavigationUI.navigateUp(navController, appBarConfiguration)
            || super.onSupportNavigateUp();
}

@Override
public void onBackPressed() {
    if (drawerLayout.isDrawerOpen(navigationView)) {
        drawerLayout.closeDrawer(navigationView);
    } else {
        super.onBackPressed();
    }
}

Item Layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true"
android:foreground="?android:attr/selectableItemBackground"
android:background="@color/quantum_white_100"
android:clickable="true"
android:focusable="false">

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="false"
    android:focusable="false"
    android:padding="8dp">
    <ImageView
        android:focusable="false"
        android:clickable="false"
        android:layout_marginTop="8dp"
        android:id="@+id/image"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:paddingBottom="5dp"
        android:paddingTop="5dp"
        tools:ignore="ContentDescription" />
    <TextView
        android:focusable="false"
        android:clickable="false"
        android:paddingBottom="5dp"
        android:paddingTop="5dp"
        android:id="@+id/type"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginStart="12dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_toEndOf="@+id/image"
        android:maxLines="1"
        android:textSize="25sp"
        android:textStyle="bold"
        android:theme="@style/HomeListViewTheme"
        tools:ignore="HardcodedTextRtlHardcoded" />
 </RelativeLayout>

  </androidx.cardview.widget.CardView>

Home fragment layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerview_fav"
    android:layout_width="match_parent"
    android:background="@color/ghostwhite"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="parent" />

  </androidx.constraintlayout.widget.ConstraintLayout>

Main Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout

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:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout

    android:id="@+id/main_container"
    android:orientation="vertical"
    android:layout_height="wrap_content"
    android:layout_width="match_parent">

    <androidx.appcompat.widget.Toolbar

        android:id="@+id/toolbar5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ToolbarTheme"
        app:titleTextAppearance="@style/ToolbarTheme" />

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar5"
        android:orientation="horizontal">

        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/nav_graph"
            tools:ignore="FragmentTagUsage" />

    </LinearLayout>

</LinearLayout>

<com.google.android.material.navigation.NavigationView
    app:headerLayout="@layout/nav_header"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:menu="@menu/nav_menu"
    app:itemTextAppearance="@style/MyMenu"
    android:layout_gravity="start"
    android:id="@+id/navigation_view" />

</androidx.drawerlayout.widget.DrawerLayout>

MenuHolder class

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

private OnItemClickListener listener;

@BindView(R.id.type)
TextView name;
@BindView(R.id.image)
ImageView img;

public MenuHolder(View itemView, OnItemClickListener listener) {
    super(itemView);
    ButterKnife.bind(this, itemView);
    itemView.setOnClickListener(this);
    this.listener = listener;
}

public void displayView(String type, int image) {
    name.setText(type);
    img.setImageResource(image);
}

public void onClick(View view) {
    listener.onClick(view, getAdapterPosition());
}

}

Interface OnItemClickListener

public interface OnItemClickListener {

void onClick(View view, int position);

}

  • 1
    i am not getting your weird way of setting the listener , in the `MenuHolder` can't you just do `itemView.setOnClickListener(v -> { your code });` – Abhinav Chauhan May 20 '20 at 12:11
  • Or you can do it like `itemView.setOnClickListener(onItemClickListener)` inside `onBindViewHolder()`, where `onItemCLickListener` is sent as a parameter from your `Activity` – SkypeDogg May 20 '20 at 12:16
  • I've updated with the code for holder and interface, I need an interface because I have to switch fragment calling NavigationController (I'm using Navigation Component) from my home fragment. – Giovanni Corte May 20 '20 at 12:39

1 Answers1

0

Make your own OnCLickListener:

public interface RecyclerViewClickListener {
        void recyclerViewListClicked(View v, int position);
    } 

Remember to create instance of it inside your adapter class

RecyclerViewClickListener itemClickListsner;

Create your custom ViewHolder class:

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

        // your fields here


        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            // get fields from XML by findViewById()

           itemView.setOnClickListener(this);

        }

        @Override
        public void onClick(View view) {
            itemClickListener.recyclerViewListClicked(itemView, this.getLayoutPosition());
        }
    }

And now you can pass your `RecyclerViewClickListener'

public HomeAdapter(RecyclerViewClickListener clickListener) {

     this.itemClickListener = clickListener;

}

And inside Actitivy:

HomeAdapter(new RecyclerViewClickListener() {
            @Override
            public void recyclerViewListClicked(View view, int position) {

        });
SkypeDogg
  • 1,000
  • 2
  • 13
  • 28
  • Now I will update with the holder and interface, but why are you suggesting to but the listener in the constructor? I'm using navigation component, I don't inizializzate any fragment – Giovanni Corte May 20 '20 at 12:36
  • In your situation `OnClickListener` is being implemented, but it looks like it happens too late and probably that's why you have to restart `Activity`. In my example, you are sure, that `OnClickListener` is delivered in time, because you send it while creating your adapter object. – SkypeDogg May 20 '20 at 12:41
  • Ok thank you, but I'm not understanding why are you passing a listener in the constructor of my fragment, when I have to set the listener to the adapter. The only purpose of my activity is holding the fragments and handling back button and toolbar menus – Giovanni Corte May 20 '20 at 12:47
  • For your case try calling your `setOnClickListener()` method before `setAdapter()` inside your code, and don't use mine and I think it should do the job. – SkypeDogg May 20 '20 at 12:47
  • Sorry, my mistake, there should be `HomeAdapter()` instead of `HomeFragment()`, thanks for catching it, I will correct it. – SkypeDogg May 20 '20 at 12:51
  • Thats true, I have updated and now I will try, but when I install the app on my phone on the emulator all runs fine, when I send the app to some friends of mine to test, they notice that annoying problem. Oh ok, so i will add it to the adapter, that is brilliant – Giovanni Corte May 20 '20 at 12:51
  • Have you tried debugging to check what happens, when you click an item? If onClick is being called? – SkypeDogg May 20 '20 at 13:13
  • I found the problem: the problem is the request permission dialog that shown first app launch after install: after declining or accepting, the click doesn't work, but after a restart it work always. But the question is, how to solve this now? – Giovanni Corte May 20 '20 at 13:17
  • When you check permission the next code which is being called is inside `onRequestPermissionResult()`. You have to put creating your `RecyclerView` there too. Then if you have permission the code will proceed normally without asking about permission, and if you ask for permission, then the code inside `onRequestPermissionResult()` will be called. You have to remember that for future. The best way is to ask for permissions at the end of method where you do it (onCreate() for ex.) so no code is being skipped. – SkypeDogg May 20 '20 at 13:24
  • You're right, I'm now setting the adapter in the permission results, but it's not working. When clicking on an item the log says W/activity: can only request one permission at a time – Giovanni Corte May 20 '20 at 13:27
  • Check here: https://stackoverflow.com/questions/42035244/getting-w-activity-can-request-only-one-set-of-permissions-at-a-time Now it's the problem with permissions, not the adapter. I can't say what exactly, because I don't see asking for permissions. – SkypeDogg May 20 '20 at 13:31
  • Solved it, I had to use requestPermission and NOT ActivityCompat.requestPermission. The response was sent to the activity, and my fragment never updated the boolean for launch the next fragment. Thank you so much for the support – Giovanni Corte May 20 '20 at 14:12