1
votes

I have an Activity with a RecyclerView which display Livedata from a room database.

My aim is to start a new Activity with more data from the room database when the user is clicking on the corresponding item in the RecyclerView. For that I overwrote the onClick() method in the adapter of the RecylcerView. Each object of the RecyclerView has a Id, I need that Id to get the corresponding data from the database. So I passed the Id from the Adapter to the Activity.

To search an element by Id in the database that I need the ViewModel object in the MainAcitivty. It is initialized in the onCreate() of the Activity. The method I called in the Adapter is outside the onCreate() and I get a null object reference exception when I try to use it.

How can I use the ViewModel outside of the onCreate() method of the Activity? Or is there another way to search for the element in the database?

Thank you!

The Adapter class: In the onClick() method is the relevant part.

package com.example.fillmyplate.activities;

import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.fillmyplate.R;
import com.example.fillmyplate.entitys.Recipe;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

class RecipeAdapter extends RecyclerView.Adapter<RecipeAdapter.RecipeViewHolder> {
    private static final String TAG = "RecipeAdapter";

    private List<Recipe> mRecipes = new ArrayList<>();

    private LayoutInflater mInflater;


    private Context mContext;

    private MainActivity mainActivity = new MainActivity();

    private static int backGroundIndex = 0;



    class RecipeViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public final TextView recipeTitleItemView;

        ImageView imageView;


        public RecipeViewHolder(View itemView) {
            super(itemView);

            recipeTitleItemView = itemView.findViewById(R.id.name);

            imageView = itemView.findViewById(R.id.card_image_view);

            Log.d(TAG, "RecipeViewHolder: index " + backGroundIndex);

            if (backGroundIndex == 0) {
                imageView.setImageResource(R.drawable.background_green);
                backGroundIndex++;
            } else if (backGroundIndex == 1 ) {
                imageView.setImageResource(R.drawable.background_red);
                backGroundIndex++;
            } else if (backGroundIndex == 2 ) {
                imageView.setImageResource(R.drawable.background_blue);
                backGroundIndex = 0;
            }
            itemView.setOnClickListener(this);


        }

        @Override
        public void onClick(View v) {

            int position = getAdapterPosition();

            // This should be the mistake.
            mainActivity.startKnownRecipeActivity(position);

        }
    }

    public RecipeAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
        this.mContext = context;
    }

    @NonNull
    @Override
    public RecipeAdapter.RecipeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // Inflate an item view
        View mRecipeTitleView = mInflater.inflate(
                R.layout.recipe_list_row, parent, false);

        return new RecipeViewHolder(mRecipeTitleView);

    }

    // Get data into the corrsponding views
    @Override
    public void onBindViewHolder(RecipeAdapter.RecipeViewHolder holder, int position) {
        Log.d(TAG, "onBindViewHolder: " + position);
        Recipe currentRecipe = mRecipes.get(position);

        Log.d(TAG, "onBindViewHolder: setText " + currentRecipe);
        holder.recipeTitleItemView.setText(currentRecipe.getTitle());

    }

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

    public void setRecipes(List<Recipe> recipes) {
        this.mRecipes = recipes;
        Log.d(TAG, "setRecipes:  notifydataChanged" );
        notifyDataSetChanged();
    }


}

MainActivity:

package com.example.fillmyplate.activities;

import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.media.Image;
import android.os.Build;
import android.os.Bundle;

import com.example.fillmyplate.R;
import com.example.fillmyplate.entitys.Recipe;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.cardview.widget.CardView;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.Room;
import androidx.room.RoomDatabase;

import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainAcitivity";

    private static final int NEW_RECIPE_ACTIVITY_REQUEST_CODE = 1;

    private RecyclerView mRecyclerView;

    private RecipeViewModel mRecipeViewmodel; 

    private RecyclerView.LayoutManager layoutManager;

    //private final List<String> mTitleList = new LinkedList<>();

    //NEU for adapter
    private List<String> recipeDataList = new ArrayList<>();

    RecipeRoomDatabase db;

    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // RECYCLER VIEW STUFF
        mRecyclerView = findViewById(R.id.recycler_view1);

        mRecyclerView.setHasFixedSize(true);

        // user linerar layout manager
        layoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(layoutManager);

        // specify an adapter

        final RecipeAdapter recipeAdapter = new RecipeAdapter(this);
        //mAdapter = new RecipeAdapter(this, mTitleList);
        mRecyclerView.setAdapter(recipeAdapter);

        mRecipeViewmodel = ViewModelProviders.of(this).get(RecipeViewModel.class);
        mRecipeViewmodel.getAllRecipes().observe(this, new Observer<List<Recipe>>() {
            @Override
            public void onChanged(List<Recipe> recipes) {
                Log.d(TAG, "onChanged: " + recipes.toString());
                for (Recipe rec : recipes) {
                    Log.d(TAG, "onChanged: " + rec.getTitle());
                    Log.d(TAG, "onChanged: recipe id " + rec.getUid());
                }
                recipeAdapter.setRecipes(recipes);
            }
        });




        // DB
        db = Room.databaseBuilder(getApplicationContext(), RecipeRoomDatabase.class, "appdb").build();



        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);


        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, AddRecipeActivity.class);
                startActivityForResult(intent, NEW_RECIPE_ACTIVITY_REQUEST_CODE);
            }
        });


    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(TAG, "onActivityResult: ");


        if (requestCode == NEW_RECIPE_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
            Log.d(TAG, "onActivityResult: " + data.getStringExtra(AddRecipeActivity.EXTRA_REPLY));
           // mTitleList.add(data.getStringExtra(AddRecipeActivity.EXTRA_REPLY));
            Recipe rec = new Recipe(data.getStringExtra(AddRecipeActivity.EXTRA_REPLY));
            mRecipeViewmodel.insert(rec);

        } else {
            Toast.makeText(
                    getApplicationContext(),
                    "saved",
                    Toast.LENGTH_LONG).show();
        }
    }

    public void startKnownRecipeActivity(int position) {

        Log.d(TAG, "startKnownRecipeActivity: Position " + position);
        LiveData<List<Recipe>> recipe = mRecipeViewmodel.findById(position);
        if (recipe.getValue().size() > 1) {
            Log.d(TAG, "startKnownRecipeActivity: Error database found more than one recipe.");
        } else {
            Log.d(TAG, "startKnownRecipeActivity: Start activity with recipe " + recipe.getValue().get(0).getTitle());
        }


    }
}
1
Are you sure that mRecipeViewmodel is null? Maybe here if (recipe.getValue().size() > 1), recipe or recipe.getValue() is null? - Elvedin Selimoski
First of all you don't create an activity by using its constructor like private MainActivity mainActivity = new MainActivity(). You start a new activity using an Intent. This mainActivity you initialised with a constructor is a different object from your real MainActivity. This instance you created doesn't have a viewModel and all that. You should create a callback in your adapter that gets used by your original MainActivity. - Razvan S.
@RazvanS. Yes you are right, I should fix that. - Philipp

1 Answers

3
votes

The thing you need to do is to use a call back to send position back to activity.

To make sure that view position is correct you need to override 3 functions in RecyclerView Adapter:

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

@Override
    public long getItemId(int position) {
        return position;
    }


 @Override
    public int getItemViewType(int position) {
        return position;
    }

For the Callback just create an Interface:

public interface AdapterListener {
void onClick(int id);
void onClick(ViewModel object);
}

Make a method in your Recycler Adapter:

private AdapterListener adapterListener;
public void setAdapterListener(AdapterListener mCallback) {
    this.adapterListener = mCallback;
}

Implement this Interface on your Activity then you will get both methods of the interface.

public class MainActivity extends AppCompatActivity implements AdapterListener{

Register the listener by calling the setAdapterListener method in your activity after the initialization of the RecyclerView

adapterObject.setAdapterListener(MainActivity.this);

Then the last thing you need to do is call interface method in your item onClickListener, where u can either pass the complete model or just the id of the model

adapterListener.onClick(modelObject.getId());

OR

adapterListener.onClick(modelObject);