I would like to build a Deckbuilder that allows you to save created decks locally on the device.
The Decklist are stored in Arraylists, called TransferDeck. Which I would like to store in room database. My issue is, that I do not know how to populate my database correctly, with the data comming out of the Arraylist.
I am used to working with Arraylist and below you see my try for storing the data:
So this is what I tried but what sadly does not work:
private void populateDB(final List<TransferDeck> mTransferDeck) {
new Thread(new Runnable() {
#Override
public void run() {
List<SaveDeck> mSaveDeck = new ArrayList<>();
for(int i = 0; i<mTransferDeck.size(); i++){
mSaveDeck.add(new SaveDeck(i, "FirstSavedDeck", mTransferDeck.get(i).getCardImage() ,mTransferDeck.get(i).getTypeImage(), mTransferDeck.get(i).getCost(), mTransferDeck.get(i).getName(), mTransferDeck.get(i).getNumber()));
}
mSavedDecksDB.deckBuilderDao().insertCards(mSaveDeck);
}
}).start();
}
Below you can find the rest of my code, but the above one should be enough to make clear what I want to do...
I created the class SaveDeck which should be able to Save a Deck with a given Deckname:
:-
#Entity
public class SaveDeck implements Serializable {
#PrimaryKey(autoGenerate = true)
private int _id;
public SaveDeck(int _id, String deckName, int cardImage, int typeImage, Integer cardCost, String cardName, Integer cardNumber) {
this._id = _id;
DeckName = deckName;
CardImage = cardImage;
TypeImage = typeImage;
CardCost = cardCost;
CardName = cardName;
CardNumber = cardNumber;
}
#ColumnInfo(name = "DeckName")
private String DeckName;
#ColumnInfo(name = "CardImage")
private int CardImage;
#ColumnInfo(name = "TypeImage")
private int TypeImage;
#ColumnInfo(name = "CardCost")
private Integer CardCost;
#ColumnInfo(name = "CardName")
private String CardName;
#ColumnInfo(name = "CardNumber")
private Integer CardNumber;
}
I created the Dao Class as follows:
:-
#Dao
public interface DeckBuilderDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
public long[] insertCards(SaveDeck... saveDecks);
#Insert(onConflict = OnConflictStrategy.IGNORE)
public long insertCard(SaveDeck saveDecks);
#Update
public int updateCardBaseEntries(SaveDeck... saveDecks);
#Update
public int updateCardBaseEntry(SaveDeck saveDecks);
#Delete
public int deleteCardBaseEntried(SaveDeck... saveDecks);
#Delete
public int deleteCardBaseEntry(SaveDeck saveDecks);
#Query("SELECT * FROM SaveDeck")
public SaveDeck[] getAllDecks();
//probably I do not need the getAllDecks Query. Right now I only need the following one:
#Query("SELECT * FROM SaveDeck WHERE DeckName = :NameOfDeck ORDER BY DeckName, CardName")
public SaveDeck getOneDeck(String NameOfDeck);
}
Furthermore created the DataBase Class:
#Database(entities = {SaveDeck.class}, version = 1)
public abstract class SaveDecksDataBase extends RoomDatabase {
public abstract DeckBuilderDao deckBuilderDao();
}
The last class is a fragment, where I try to populate my database, and in the populateDB() class is the issue
public class review_fragment extends Fragment {
private List<TransferDeck> mTransferDeck = DataHolder.getInstance().savedDecklistTransfer;
SaveDecksDataBase mSavedDecksDB;
Cursor mCursor;
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//return super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.review_fragment, container, false);
/*Introduce Cards Recycler*/
RecyclerView rvCards = view.findViewById(R.id.rv_review_cardlist);
rvCards.setLayoutManager(new GridLayoutManager(getActivity(), 5));
review_RViewAdapter_Cards adapterCards = new review_RViewAdapter_Cards(getContext(), mTransferDeck);
rvCards.setAdapter(adapterCards);
/*Init Room database*/
mSavedDecksDB = Room.databaseBuilder(getActivity(),SaveDecksDataBase.class,"SavedDecksDB.db").build();
populateDB(mTransferDeck);
return view;
}
private void populateDB(final List<TransferDeck> mTransferDeck) {
new Thread(new Runnable() {
#Override
public void run() {
List<SaveDeck> mSaveDeck = new ArrayList<>();
for(int i = 0; i<mTransferDeck.size(); i++){
mSaveDeck.add(new SaveDeck(i, "FirstSavedDeck", mTransferDeck.get(i).getCardImage() ,mTransferDeck.get(i).getTypeImage(), mTransferDeck.get(i).getCost(), mTransferDeck.get(i).getName(), mTransferDeck.get(i).getNumber()));
}
mSavedDecksDB.deckBuilderDao().insertCards(mSaveDeck);
}
}).start();
}
}
I like to mention that this should be a comment rather than an answer.
First, either use AysncTask or use more robust Executors.newSingleThreadExecutor(). If you prefer the second one then it's best if you create a helper class (example). Example:
private void populateDB(final List<TransferDeck> mTransferDeck) {
AppExecutors.diskIO().execute(() -> {
for(int i = 0; i<mTransferDeck.size(); i++){
mSavedDecksDB.deckBuilderDao().insertCards(new SaveDeck(...);
}
});
}
(1) Create a blank constructor.
(4) Room Database should not be initialized there and it's best if it's singleton. So the your database class (3) can be like:
public abstract class SaveDecksDataBase extends RoomDatabase {
private static SaveDecksDataBase sINSTANCE;
private static final Object LOCK = new Object();
public static SaveDecksDataBase getDatabase(final Context context) {
if (sINSTANCE == null) {
synchronized (LOCK) {
if (sINSTANCE == null) {
sINSTANCE = Room.databaseBuilder(context.getApplicationContext(),
SaveDecksDataBase.class, "SavedDecksDB.db")
.build();
}
}
}
return sINSTANCE;
}
public abstract DeckBuilderDao deckBuilderDao();
}
Lastly, to get SaveDeck object you also has to use Executors or AsyncTask to do the work in background, and then populate the RecyclerView.
Android Room Database
Practice set
Related
I'm trying to complete this small in class exercise where we have to save data using Room Database and then display that information in a ListView.
This app is about a player. The player has 4 fields (Id, Name, Position, Number of Goals scored).
The ID of the very first player saved should be #1, #2 for the second player and so on.
The user must enter the name of the player through the EditText field.
There are then three radio buttons of which, the user has to select one in order to choose their position (Goalie, Defence, Forward). Finally, the last field the user must enter the number of goals this player has scored through the use of the EditText field.
Finally, the last field the user must enter the number of goals this player has scored through the use of the EditText field.
Once the user clicks the "SAVE" button, all the fields previously selected will be cleared and their will be a quick toast message displaying the ID# of the player.
In order to view all the saved data the user must click the "VIEW ALL" button which will take them to the second activity and display the Name of the Player, Position, Number of Goals scored in the next activity.
I'm not too familiar with room so whenever I press the "Save"Save button my app crashes. Any help would be greatly appreciated, Thanks!
MainActivity.Java
public class MainActivity extends AppCompatActivity {
private EditText playerEdt, goalsEdt;
private int id = 0;
private RadioGroup groupRad;
private RadioButton radioButton;
private MyDatabase myDb;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playerEdt = findViewById(R.id.edtPlayer);
goalsEdt = findViewById(R.id.edtGoals);
myDb = MyDatabase.getInstance(MainActivity.this);
}
public void saveData(View view) {
id++;
String name = playerEdt.getText().toString();
groupRad = findViewById(R.id.radGroup);
int selectedId = groupRad.getCheckedRadioButtonId();
radioButton = findViewById(selectedId);
String position = radioButton.getText().toString();
String goalsString = goalsEdt.getText().toString();
int goals = Integer.valueOf(goalsString);
Player player = new Player(id, name, position, goals);
myDb.playerDao().insert(player);
playerEdt.setText("");
goalsEdt.setText("");
groupRad.clearCheck();
}
public void viewData(View view) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
}
SecondActivity.Java
public class SecondActivity extends AppCompatActivity {
ListView listView;
MyDatabase database;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
listView = findViewById(R.id.listView);
database = MyDatabase.getInstance(SecondActivity.this);
List<Player> players = database.playerDao().getAll();
ArrayAdapter adapter = new ArrayAdapter(SecondActivity.this, android.R.layout.simple_list_item_1, players);
listView.setAdapter(adapter);
}
}
Player.Java
#Entity
public class Player {
#PrimaryKey
private int id;
#ColumnInfo(name = "player_name")
private String name;
#ColumnInfo(name = "player_position")
private String position;
#ColumnInfo(name = "player_goals")
private int goals;
public Player(int id, String name, String position, int goals) {
this.id = id;
this.name = name;
this.position = position;
this.goals = goals;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public int getGoals() {
return goals;
}
public void setGoals(int goals) {
this.goals = goals;
}
}
PlayerDao.java
#Dao
public abstract class PlayerDao {
#Insert
public abstract void insert (Player player);
#Delete
public abstract void delete (Player player);
#Update
public abstract void update (Player player);
#Query("select * from Player")
public abstract List<Player> getAll();
}
MyDatabase.Java
#Database(entities = Player.class, version = 1)
public abstract class MyDatabase extends RoomDatabase {
public abstract PlayerDao playerDao();
private static MyDatabase instance;
public static MyDatabase getInstance(Context context){
if( instance == null){
instance = Room.databaseBuilder(context, MyDatabase.class, "PlayerDb")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
activity_main.xml: https://www.codepile.net/pile/d5rq8mx2
activity_second.xml: https://www.codepile.net/pile/2vbYzXq3
It would appear that you have a number of issues:-
You need to have getters for all the members of the Player class.
These were added (and setters) :-
public int getGoals() {
return goals;
}
public void setGoals(int goals) {
this.goals = goals;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
The core issue is that the attemps to find the views in the saveData is returning nulls and hence you are getting a null pointer exception (NPE) because the views don't exist within the SAVE button.
The solution is to find the views (i.e use findViewById in the onCreate method).
There is also a lack of data verification which will causes issue. The following version of MainActivity.java handles the issues :-
public class MainActivity extends AppCompatActivity {
private EditText playerEdt, goalsEdt;
private int id = 0;
private RadioGroup groupRad;
private RadioButton radioButton;
private MyDatabase myDb;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playerEdt = findViewById(R.id.edtPlayer);
goalsEdt = findViewById(R.id.edtGoals);
groupRad = findViewById(R.id.radGroup);
myDb = MyDatabase.getInstance(MainActivity.this);
}
public void saveData(View view) {
id++;
String name = playerEdt.getText().toString();
if (name.length() < 1) {
Toast.makeText(view.getContext(),"Player Name is blank. Try again.",Toast.LENGTH_SHORT).show();
playerEdt.requestFocus();
}
radioButton = findViewById(groupRad.getCheckedRadioButtonId());
if (radioButton == null) {
Toast.makeText(view.getContext(),"You must select Goalie Defence or Forward. Try again",Toast.LENGTH_SHORT).show();
return;
}
String position = radioButton.getText().toString();
String goalsString = goalsEdt.getText().toString();
int goals = 0;
try {
goals = Integer.valueOf(goalsString);
} catch (Exception e) {
Toast.makeText(view.getContext(),"You must give the number of Goals. try again.",Toast.LENGTH_SHORT).show();
goalsEdt.requestFocus();
}
Player player = new Player(id, name, position, goals);
if (myDb.playerDao().insert(player) < 1) {
Toast.makeText(view.getContext(),"Player not Added (duplicate)",Toast.LENGTH_SHORT).show();
return;
}
playerEdt.setText("");
goalsEdt.setText("");
groupRad.clearCheck();
Toast.makeText(view.getContext(),"Player Added. Name is " + name + " Position is " + position + " Goals = " + goals,Toast.LENGTH_SHORT).show();
}
public void viewData(View view) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
}
Additionally PlayerDao.java has been changed to not fail if the player name is duplicated and to also return useful values when inserting, deleting or updating. it is :-
#Dao
public abstract class PlayerDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract long insert (Player player);
#Delete
public abstract int delete (Player player);
#Update
public abstract int update (Player player);
#Query("select * from Player")
public abstract List<Player> getAll();
}
First of all, it would be better if you use autoGenerate = true in your Player.java class for id:
#Entity
public class Player {
#PrimaryKey(autoGenerate = true)
private int id;
By doing this you don't have to give your players ids and Room does the job for you.
And for the app crash when saving, you must check the log and see what is causing the app to crash. Update your question with that so users can help you.
Trying to get the insert to return the PK so I can capture it in a var and pass it to other activities.
DAO:
#Dao public interface NoteDao {
#Insert
long insert(Note note);
Repository:
private MutableLiveData<Long> noteInsertedLiveData = new MutableLiveData();
private NoteDao noteDao;
private LiveData<List<Note>> allNotes;
public NoteRepository(Application application) {
NoteDatabase database = NoteDatabase.getInstance(application);
noteDao = database.noteDao();
allNotes = noteDao.getAllNotes2();
}
public void insert(OnTaskCompleted onTaskCompleted, Note note) {
new InsertAsyncTask(noteDao, onTaskCompleted).execute(note);
}
public LiveData<Long> getNoteInsertedLiveData() {
return noteInsertedLiveData;
}
ASync:
private static class InsertNoteAsyncTask extends AsyncTask<Note, Void, Long> {
private long sqCbtId = -1;
private NoteDao noteDao;
private OnTaskCompleted onTaskCompleted;
private InsertNoteAsyncTask(NoteDao noteDao, OnTaskCompleted onTaskCompleted) {
this.noteDao = noteDao;
this.onTaskCompleted = onTaskCompleted;
}
#Override
protected Long doInBackground(Note... notes) {
return noteDao.insert(notes[0])
}
#Override
protected void onPostExecute(Long result) {
onTaskCompleted.onTaskCompleted(result);
}
}
View model:
public long insert(Note note){
sqCbtId = repository.insert(note); return sqCbtId; }
Activity :
Note note = new Note(userId, therapistId, automaticThoughtString, distortions, challengeThoughtString, alternativeThoughtString, postedWorkout);
sqCbtId = noteViewModel.insert(note);
Entity:
#Entity(tableName = "note_table")
public class Note {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "cbtId")
private long sqCbtId;
#NonNull
#ColumnInfo(name = "userId")
private int userId;
#NonNull
#ColumnInfo(name = "therapistId")
private int therapistId;
#NonNull
#ColumnInfo(name = "automaticThought")
private String automaticThought;
#NonNull
#ColumnInfo(name = "distortions")
private int distortions;
#NonNull
#ColumnInfo(name = "challengeThought")
private String challengeThought;
#NonNull
#ColumnInfo(name = "alternativeThought")
private String alternativeThought;
#NonNull
#ColumnInfo(name = "postedWorkout")
private String postedWorkout;
I have one error on this line:
new InsertAsyncTask(noteDao, onTaskCompleted).execute(note)
It states that the InsertASyncTask cannot be applied to the paramteters (naoteDao, onTaskCompleted)
SQLite and hence Room's #Insert implementation returns the row id of the inserted row. If the primary key is not auto generated you already know it, provided inserted didn't return -1 which signifies error. If the Primary key is auto generated you would need to query it again.
Since your Primary Key is of type long, ie, INTEGER for SQLite, the rowId becomes the Primary Key for the row. See this: https://www.sqlite.org/rowidtable.html
Hence in this case you do not need to query again for the Primary Key. Simply use the rowId returned by #Insert (provided not -1).
Errors on your code:
sqCbtId = note.getSqCbtId(); return sqCbtId;
This is getting the sqCbtId from the object you have just inserted into the database. The id is not generated for this object. It is stored in database. Get the id from the AsyncTask's onPostExecute
Code: (ignore any compilation errors or typo, not written on IDE :P)
class ViewModel
{
private MutableLiveData<Long> noteInsertedLiveData = new MutableLiveData();
public long insert(Note note) {
new InsertNoteAsyncTask(noteDao, new InsertNoteAsyncTask.Listener {
#override
void onNoteInserted(Long sqCbtId) {
noteInsertedLiveData.setValue(sqCbtId);
}
}).execute(note);
sqCbtId = note.getSqCbtId(); return sqCbtId;
}
public LiveData<Long> getNoteInsertedLiveData() {
return noteInsertedLiveData;
}
private static class InsertNoteAsyncTask extends AsyncTask<Note, Void, Long> {
private long sqCbtId = -1;
private NoteDao noteDao;
private Listener listener;
private InsertNoteAsyncTask(NoteDao noteDao, Listener listener) {
this.noteDao = noteDao;
this.listener = listener;
}
#Override
protected Long doInBackground(Note... notes) {
sqCbtId = noteDao.insert(notes[0]);
return sqCbtId;
}
#Override
protected void onPostExecute(Long result) {
listener.onNoteInserted(result);
}
interface Listener {
void onNoteInserted(Long sqCbtId);
}
}
}
On your Activity
noteViewModel.getNoteInsertedLiveData().observe(this, /** add Observer here to get update on id after every insert in db **/);
Note note = new Note(userId, therapistId, automaticThoughtString, distortions, challengeThoughtString, alternativeThoughtString, postedWorkout);
noteViewModel.insert(note);
private static class InsertNoteAsyncTask extends AsyncTask<Note, Void, Void> {
private long sqCbtId = -1;
private NoteDao noteDao;
private Context context;
private InsertNoteAsyncTask(NoteDao noteDao) {
this.noteDao = noteDao;
}
#Override
protected Void doInBackground(Note... notes) {
sqCbtId = noteDao.insert(notes[0]);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
getCbtId(sqCbtId);
Log.d(TAG, "onPostExecute: " + sqCbtId);
}
public void getCbtId(long cbtId) {
sqCbtId = cbtId;
}
}
This is the code I used which captures the row id
I have the CidVo class:
#Entity
public class CidVo implements Serializable {
private static final long serialVersionUID = 6128323407787450445L;
#NonNull
#PrimaryKey
private String idCid;
private String dsCid;
//private StatusType idStatus;
public CidVo() {
super();
}
public String getIdCid() {
return idCid;
}
public void setIdCid(String idCid) {
this.idCid = idCid;
}
public String getDsCid() {
return dsCid;
}
public void setDsCid(String dsCid) {
this.dsCid = dsCid;
}
/*public StatusType getIdStatus() {
return idStatus;
}
public void setIdStatus(StatusType idStatus) {
this.idStatus = idStatus;
} */
}
The DAO class:
#Dao
public interface CIDDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAllCID(ArrayList<CidVo>... cidVos);
}
I get a list of objects, via JSON. And I would like to save it in the database.
But, I get this return error when trying to compile:
Error:Entity class must be annotated with #Entity
E:\workspace_android_studio\app_atendimento_branch_test\app\src\main\java\br\com\sisteplan\app\atendimento\Model\CIDDao.java
Error:(19, 43) error: Type of the parameter must be a class annotated
with #Entity or a collection/array of it.
How to solve this question?
The following is the postExecute:
#Override
protected void onPostExecute(AsyncTaskResult<Retorno> respAtestadoVo) {
super.onPostExecute(respAtestadoVo);
if (respAtestadoVo.getExceptionResult() == null) {
RetornoCid retornoCid = (RetornoCid) respAtestadoVo.getResult();
ArrayList<CidVo> cidVos = (ArrayList<CidVo>) retornoCid.getRetorno();
appDataBase.getCidDao().insertAllCID(cidVos);
Toast.makeText(context, "Sucesso", Toast.LENGTH_SHORT).show();
}
}
Instance:
#Database(entities = {CidVo.class}, version = 1)
public abstract class AppDataBase extends RoomDatabase {
public abstract CIDDao getCidDao();
private static AppDataBase appDataBase;
public static AppDataBase getInstance(Context context){
if(null == appDataBase){
appDataBase = buildDataBaseInstance(context);
}
return appDataBase;
}
private static AppDataBase buildDataBaseInstance(Context context){
return Room.databaseBuilder(context,
AppDataBase.class,
"app_database")
.allowMainThreadQueries().build();
}
private void cleanUp(){
appDataBase = null;
}
}
The reason is a vararg notation here void insertAllCID(ArrayList<CidVo>... cidVos);
You're trying to insert an array of array lists.
So, just replace it with
void insertAllCID(List<CidVo> cidVos);
Also, it's better to use interfaces over implementations, when you're working with collections. So, you can change this part
RetornoCid retornoCid = (RetornoCid) respAtestadoVo.getResult();
ArrayList<CidVo> cidVos = (ArrayList<CidVo>) retornoCid.getRetorno();
to this
RetornoCid retornoCid = (RetornoCid) respAtestadoVo.getResult();
List<CidVo> cidVos = new ArrayList(retornoCid.getRetorno());
I have been beating my head against the wall and I cannot understand why this is happening. I am working with the new Architectural Components for Android and I am having problems updating a LiveData with a List of Objects.
I have two spinners. When i change the option in the first one, The second one must have its content changed. But this last part is not happening.
Can anyone help me?
State.java
#Entity(tableName = "states")
public class State{
#PrimaryKey(autoGenerate = false)
private int id;
private String name;
#ColumnInfo(name = "countryId")
private String CountryId;
#Ignore
private Object geoCenter, geoLimit;
public State(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCountryId() {
return CountryId;
}
public void setCountryId(String countryId) {
CountryId = countryId;
}
}
StateDAO
#Dao
public interface StateDao {
#Query("SELECT * FROM states")
LiveData<List<State>> getAllStates();
#Query("SELECT * FROM states WHERE countryId = :countryID")
LiveData<List<State>> getStatesFromCountry(String countryID);
#Query("SELECT COUNT(*) FROM states")
int getNrStates();
#Query("SELECT COUNT(*) FROM states WHERE countryId = :countryID")
int getNrStatesByCountry(String countryID);
#Insert(onConflict = IGNORE)
void insertAll(List<State> states);
#Delete
void delete(State state);
}
StateRepository
#Singleton
public class StatesRepository {
private final WebServices services;
private final StateDao stateDao;
private final Executor executor;
#Inject
public StatesRepository(Executor executor, StateDao stateDao, WebServices services) {
this.services = services;
this.stateDao = stateDao;
this.executor = executor;
}
public LiveData<List<State>> getStates(String token){
refreshStates(token);
return stateDao.getAllStates();
}
public LiveData<List<State>> getStatesFromCountry(String countryID){
return stateDao.getStatesFromCountry(countryID);
}
private void refreshStates(final String token){
executor.execute(() -> {
Log.d("oooooo", stateDao.getNrStates() + "");
if(stateDao.getNrStates() == 0){
try {
Response<List<State>> response = services.getStates("Bearer "+token).execute();
stateDao.insertAll(response.body());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
StateViewModel
public class StatesViewModel extends ViewModel {
private LiveData<List<State>> states;
private StatesRepository repo;
#Inject
public StatesViewModel(StatesRepository repository){
this.repo = repository;
}
public void init(String token){
states = repo.getStates(token);
}
public void getStatesFromCountry(String countryID){
states = repo.getStatesFromCountry(countryID);
}
public LiveData<List<State>> getStates(){
return this.states;
}
}
Fragment
public class EditAddressFragment extends LifecycleFragment implements View.OnClickListener, Injectable{
private Spinner country, city, state, zip_code;
private String token;
private List<Country> countries;
private List<City> cities;
private List<State> states;
#Inject ViewModelFactory viewModelFactory;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.addresses_edit_layout, container, false);
city = view.findViewById(R.id.city);
state = view.findViewById(R.id.state);
country = view.findViewById(R.id.country);
...
countries = new ArrayList<>();
cities = new ArrayList<>();
states = new ArrayList<>();
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
CountrySpinnerAdapter adapter = new CountrySpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, countries);
country.setAdapter(adapter);
CitySpinnerAdapter cityAdapter = new CitySpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, cities);
city.setAdapter(cityAdapter);
StateSpinnerAdapter stateAdapter = new StateSpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, states);
state.setAdapter(stateAdapter);
CountriesViewModel countriesViewModel = ViewModelProviders.of(this, viewModelFactory).get(CountriesViewModel.class);
countriesViewModel.init(token);
countriesViewModel.getCountries().observe(this, adapter::setValues);
CityViewModel cityViewModel = ViewModelProviders.of(this, viewModelFactory).get(CityViewModel.class);
cityViewModel.init(token);
cityViewModel.getCities().observe(this, cityAdapter::setValues);
StatesViewModel statesViewModel = ViewModelProviders.of(this, viewModelFactory).get(StatesViewModel.class);
statesViewModel.init(token);
statesViewModel.getStates().observe(this, states -> {
Log.d("called", states.toString());
stateAdapter.setValues(states); } );
country.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
Country c = (Country) adapterView.getItemAtPosition(i);
Log.d("cd", c.getId());
//states = new ArrayList<State>();
statesViewModel.getStatesFromCountry(c.getId());
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
....
Adapter
public void setValues(List<State> states)
{
this.states = states;
Log.d("s", states.isEmpty()+" "+states.toString());
notifyDataSetChanged();
}
Well, I have reached a solution for this issue and found out how this LiveData things works.
Thanks to #MartinMarconcini for all his help is debugging ;)
So apparently, the observers are linked to the object you first set it up to. You cannot replace the object (by attribution) or otherwise it will not work.
Also, if the value of your variable is going to change then you should use MutableLiveData
So the change necessary were:
1. Change from LiveData to MutableLiveData and pass that MutableLiveData to the repository when you need to update it
public class StatesViewModel extends ViewModel {
private MutableLiveData<List<State>> states; ;;CHANGED
private StatesRepository repo;
#Inject
public StatesViewModel(StatesRepository repository){
this.repo = repository;
}
public void init(String token){
states = repo.getStates(token);
}
public void getStatesFromCountry(String countryID){
repo.getStatesFromCountry(this.states, countryID); ;;CHANGED
}
public LiveData<List<State>> getStates(){
return this.states;
}
}
2. In the repository, update the MutableLiveData using setValue
#Singleton
public class StatesRepository {
private final WebServices services;
private final StateDao stateDao;
private final Executor executor;
#Inject
public StatesRepository(Executor executor, StateDao stateDao, WebServices services) {
this.services = services;
this.stateDao = stateDao;
this.executor = executor;
}
public MutableLiveData<List<State>> getStates(String token){
refreshStates(token);
final MutableLiveData<List<State>> data = new MutableLiveData<>();
data.setValue(stateDao.getAllStates());
return data;
}
;; CHANGED
public void getStatesFromCountry(MutableLiveData states, final String countryID){
states.setValue(stateDao.getStatesFromCountry(countryID));
}
private void refreshStates(final String token){
executor.execute(() -> {
if(stateDao.getNrStates() == 0){
try {
Response<List<State>> response = services.getStates("Bearer "+token).execute();
stateDao.insertAll(response.body());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
3. Changed the DAO to return List instead of LiveData>
#Dao
public interface StateDao {
#Query("SELECT * FROM states")
List<State> getAllStates();
#Query("SELECT * FROM states WHERE ctrId = :countryID")
List<State> getStatesFromCountry(String countryID);
#Query("SELECT COUNT(*) FROM states")
int getNrStates();
#Query("SELECT COUNT(*) FROM states WHERE ctrId = :countryID")
int getNrStatesByCountry(String countryID);
#Insert(onConflict = IGNORE)
void insertAll(List<State> states);
#Delete
void delete(State state);
}
4.Finally allow to perform queries in the main thread
AppModule.java
#Singleton #Provides
AppDatabase provideDb(Application app) {
return Room.databaseBuilder(app, AppDatabase.class,"unitail.db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
Dao must be same across all operations.
You use different Dao instance for insert and observe
You should not update the livedata reference once you set it and start observing it.Instead to update the live data with repository you should use the MediatorLiveData.
In your case do the following changes:
private MediatorLiveData<List<State>> states; // change
.....
.....
states.addSource(repo.getStatesFromCountry(countryID), newData -> states.setValue(newData)); //change
Writing an answer for better discussion.
So I have (in Kotlin, sry) a model that is a list of notes (it’s just a sandbox app to play w/all this) and here’s my architecture: I don’t have a Repo, but I have Activity -> ViewModel -> Dao.
So Dao exposes a LiveData<MutableList<Note>>
#Query("SELECT * FROM notes")
fun loadAll(): LiveData<MutableList<Note>>
My ViewModel… exposes it through:
val notesList = database.notesDao().loadAll()
and my Activity (onCreate) does…
viewModel.notesList.observe(this,
Observer<MutableList<Note>> { notes ->
if (notes != null) {
progressBar?.hide()
adapter.setNotesList(notes)
}
})
This works. The adapter is a RecyclerView adapter that does literally nothing but:
fun setNotesList(newList: MutableList<Note>) {
if (notes.isEmpty()) {
notes = newList
notifyItemRangeInserted(0, newList.size)
} else {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return notes.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return notes[oldItemPosition].id == newList[newItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val (id, title, _, priority) = newList[newItemPosition]
val (id1, title1, _, priority1) = notes[oldItemPosition]
return id == id1
&& priority == priority1
&& title == title1
}
})
notes = newList
result.dispatchUpdatesTo(this)
}
}
If ANY other part of the app modifies that list of notes, the adapter updates automagically. I hope this gives you a playground to try a simple(r?) approach.
Working with room persistence, when trying to get the database to insert or make select in the items, the error appears:
AppDataBase.getMovieDao()' on a null object reference
The classes related to the process are as follows:
AppDataBase class:
#Database(entities = {Movies.class}, version = 1)
public abstract class AppDataBase extends RoomDatabase {
public static final String DB_NAME = "Movies";
private static AppDataBase INSTANCE;
public static AppDataBase getDataBase(Context context){
if (INSTANCE == null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDataBase.class,DB_NAME).build();
}
return INSTANCE;
}
public abstract MovieDao getMovieDao();
}
Dao Class:
#Dao
public interface MovieDao {
#Insert
void insertAll(Movies movies);
#Update
void updateAll(Movies... notes);
#Query("SELECT * FROM moviestb")
List<Movies> getAll();
#Delete
void deleteAll(Movies... notes);
}
Entity class:
#Entity(tableName = "moviestb")
public class Movies {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "idmovie")
private long id;
#ColumnInfo(name = "titlemovie")
private String titlemovie;
........
}
Searching for the registrations:
public void loadFromDB(){
db.getDataBase(view.getContext());
if(db.getMovieDao().getAll().size() > 0){
adapter.setResults(db.getMovieDao().getAll());
}else{
Toast.makeText(view.getContext(),"Não há filmes cadastrados",Toast.LENGTH_SHORT).show();
view.getActivity().finish();
}
}
Insert:
public View.OnClickListener onSaveClick(final String plot, final String diretor, final String autor,
final String nome, final String tipo, final String ano, final String ator, final String imdb) {
return new View.OnClickListener() {
#Override
public void onClick(View itemView) {
db.getDataBase(view.getContext());
Movies movies = new Movies(nome,plot,imdb,"",ator,ano,tipo,diretor,autor);
new InsertAsyncTask(db).execute(movies);
}
};
}
private class InsertAsyncTask extends AsyncTask<Movies,Void,Void>{
private AppDataBase db;
public InsertAsyncTask(AppDataBase appDataBase) {
db = appDataBase;
}
#Override
protected Void doInBackground(Movies... params) {
db.getMovieDao().insertAll(params[0]);
return null;
}
}
Searching all:
public void loadFromDB(){
db.getDataBase(view.getContext());
if(db.getMovieDao().getAll().size() > 0){
adapter.setResults(db.getMovieDao().getAll());
}else{
Toast.makeText(view.getContext(),"Não há filmes cadastrados",Toast.LENGTH_SHORT).show();
view.getActivity().finish();
}
}
The crash occurs while fetching the database, what am I doing wrong? thank you!
db is null, apparently. Your question does not show where you ever assign it a value.
Please note that getDataBase() is a static method. Perhaps you should have a db=AppDataBase.getDataBase() statement somewhere.