Android: Recyclerview in Bottom Navigation Bar Fragment populated with SQLite will not update

1.2k Views Asked by At

I have a BottomNavigationBar with 3 fragments. In the first fragment, I try to put SQLite data into a recyclerview. It works fine except for the fact that I need to switch between the Navigation Bar items in order to see the refreshed recyclerview. When I use a handler with postDelayed however, it does show the refreshed recyclerview if I set around 1 sec of delay. 0.2 secs wont work already.

Even though this is still very generic: is there any best practice for this? It seems to me that I need to use AsyncTask which has been -however- deprecated.

Thanks!

Simon

HomeFragment

public class HomeFragment extends Fragment {


    private HomeViewModel homeViewModel;
    private Context context;
    private CardView cardview;
    private LinearLayout.LayoutParams layoutparams;
    private TextView textview;
    private RelativeLayout relativeLayout;
    private myDbAdapter helper;
    RecyclerView myView;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        homeViewModel =
                new ViewModelProvider(this).get(HomeViewModel.class);
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        
        helper  = new myDbAdapter(getContext());
        myView = (RecyclerView) root.findViewById(R.id.recyclerview_home);
        RecyclerViewAdapter3 adapter = new RecyclerViewAdapter3(new ArrayList<String>(Arrays.asList(helper.classes())));
        myView.setHasFixedSize(true);
        myView.setAdapter(adapter);
        LinearLayoutManager llm = new LinearLayoutManager(getContext());
        llm.setOrientation(LinearLayoutManager.VERTICAL);
        myView.setLayoutManager(llm);
    

        homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
        return root;
    }

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



    }

    public void refresh(View v){
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            public void run() {
                myView = (RecyclerView) v.findViewById(R.id.recyclerview_home);
                helper  = new myDbAdapter(v.getContext());
                ArrayList<String> classes = new ArrayList(Arrays.asList(helper.classes()));
                ArrayList<String> subClasses = new ArrayList(Arrays.asList(helper.subClasses()));
                RecyclerViewAdapter3 adapter = new RecyclerViewAdapter3(classes);
                myView.setHasFixedSize(true);
                myView.setAdapter(adapter);
                LinearLayoutManager llm = new LinearLayoutManager(v.getContext());
                llm.setOrientation(LinearLayoutManager.VERTICAL);
                myView.setLayoutManager(llm);
            }
        }, 1000); //time in millis
    }
}

RecyclerViewAdapter3

public class RecyclerViewAdapter3 extends RecyclerView.Adapter<RecyclerViewAdapter3.MyViewHolder> {
    public ArrayList<String> classArrayList;
    public ArrayList<String> subClassArrayList;
    myDbAdapter helper;


    public RecyclerViewAdapter3(ArrayList<String> classArrayList){
        this.classArrayList = classArrayList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View listItem = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview, parent, false);
        return new MyViewHolder(listItem);

    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.class.setText(classArrayList.get(position));

        holder.delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                helper = new myDbAdapter(v.getContext());
                helper.delete(classArrayList.get(position));
                HomeFragment homeFragment = new HomeFragment();
                homeFragment.refresh(v.getRootView());
            }
        });
        holder.selectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {


        }});}

    @Override
    public int getItemCount() {

        return classArrayList.size();
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        private TextView class;
        private Button selectButton;
        private ImageView delete;
        public MyViewHolder(View itemView) {
            super(itemView);
            class = (TextView)itemView.findViewById(R.id.name);
            selectButton = (Button) itemView.findViewById(R.id.selectButton);
            delete = (ImageView) itemView.findViewById(R.id.delete);
        }
    }
}
1

There are 1 best solutions below

2
On

Thanks for posting your code :)

There are a fair few things that can go wrong in your code as it is right now, and I can't really pinpoint what causes it to work when you use postDelay. I'm going to list a few, which you can look into:

From your onClick() inside your ViewHolder
   HomeFragment homeFragment = new HomeFragment();
   homeFragment.refresh(v.getRootView());

You should really not instantiate your fragments like this. You can instead pass a callback from your fragment to your adapter (eg.: View.OnClickListener)

You keep re-instantiating your adapter and your helper needlessly. You should create your adapter only once, set it as your recycler view adapter, and save it in a member variable.

Proposed solution

I see that you're already using ViewModel, so you're on a great path for a less error-prone screen, so I suggest that you move your db query-ing logic to your view model. If you're using raw SQLite (instead of Room), you can extend AndroidViewModel, so you'll have access to a context right away. And as you do with your homeViewModel.getText(), you should expose the classes array as live data, observe it, and submit the new list to your adapter.

For submitting your list to your adapter I suggest using ListAdapter, this will provide you a submitList method for submitting the list in the fragment, and inside the adapter, you will have a getItem(int position) method, which you can query inside the onBindViewHolder method.

Inside your fragment, it'll look something like this:

ClassAdapter adapter = null;

View onCreateView(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState)
    View root = inflater.inflate(R.layout.fragment_home, container, false);
    
    adapter = new ClassAdapter(
        new ClassDeleteCallback() {
            @Override
            void onClassDeleted(Class class) {
                // inside view model we can modify db state, than refresh live data
                viewModel.deleteClass(class);
            }
        },
        new ClassSelectedCallback() {
            // follows same pattern of above
        }
    );
    RecyclerView rv = root.findViewById(R.id.my_rv);
    rv.setAdapter(adapter);
    rv.setLayoutManager(new LinearLayoutManager(getContext());
    
        
    homeViewModel.getClasses().observe(getViewLifecycleOwner(), new Observer<List<Class>>() {
        @Override
        public void onChanged(@Nullable List<Class> classes) {
            adapter.submitList(classes);
         }
     });

    homeViewModel.refreshClasses();
    return root;
}

I can highly recommend for you to study this project a bit, because it covers lot of the basics which can lead to a much stabler app: Sunflower sample app

I think you should read a bit more about the architecture components, and then go through some code-labs and stuff, and have another go with this screen starting from square one, because it will be easier than fixing the current state :)

I hope this was helpful, and not too discouraging!