Data fetched from server is copying multiple times in room database - android

In my app I am using room as a database cache.I am fetching data from server using retrofit library and saving the data inside room database and showing the same data from room in recycler view.
Problem: Whenever activity starts it fetches data from server and saving it in room database due to which same data showing multiple times in recycler view.
What I want: I want to know how can I check if data already present in room should not save multiple time in room and if some new data updated on server it should fetch new data and save it to the room database.
This is what I have done so far:
UserDao.java
#Dao
public interface UserDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void Insert(User... users);
#Query("SELECT * FROM Users")
LiveData<List<User>> getRoomUsers();
}
User.java
#Entity(tableName = "Users")
public class User {
#PrimaryKey
private String id;
#ColumnInfo(name = "name")
#SerializedName("name")
#Expose
private String name;
#ColumnInfo(name = "age")
#SerializedName("age")
#Expose
private String age;
public User(String id,String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
UserRepository.java
public class UserRepository {
private Context context;
private UserDb userDb;
private LiveData<List<User>> listLiveData;
public UserRepository(Context context) {
this.context = context;
userDb = UserDb.getInstance(context);
listLiveData = userDb.userDao().getRoomUsers();
}
public void getUserList(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<User>> userList = apiService.getUser();
userList.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, final Response<List<User>> response) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
if(response.body() != null) {
List<User> list = response.body();
for (int i = 0; i < list.size(); i++) {
String names = list.get(i).getName();
String age = list.get(i).getAge();
String id = UUID.randomUUID().toString();
User user = new User(id,names,age);
userDb.userDao().Insert(user);
}
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Toast.makeText(context,"Data inserted",Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
Toast.makeText(context,e.getMessage(),Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Toast.makeText(context,t.getMessage(),Toast.LENGTH_LONG).show();
}
});
}
public LiveData<List<User>> getRoomUsers(){
return listLiveData;
}
}
UserViewModel.java
public class UserViewModel extends AndroidViewModel {
private UserRepository repo;
private LiveData<List<User>> listLiveData;
public UserViewModel(#NonNull Application application) {
super(application);
repo = new UserRepository(application);
listLiveData = repo.getRoomUsers();
}
public LiveData<List<User>> getListLiveData() {
return listLiveData;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
UserRepository userRepository;
RecyclerView recyclerView;
UserViewModel userModel;
List<User> userList;
UserAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
userRepository = new UserRepository(this);
userModel = ViewModelProviders.of(this).get(UserViewModel.class);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userList = new ArrayList<>();
adapter = new UserAdapter(userList,this);
recyclerView.setAdapter(adapter);
userModel.getListLiveData().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(List<User> users) {
adapter.setUserList(users);
}
});
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(MainActivity.this,AddUser.class);
startActivity(i);
}
});
userRepository.getUserList();
}
In MainActivity.java userRepository.getUserList() fetches data from server and save it in room database.
Some one please let me know how can I get desired result. Any help would be appreciated.
THANKS

As #a_local_nobody says add this to your Dao.
#Insert(onConflict = OnConflictStrategy.REPLACE)
But this will not fully solve your problem. If you see SQLite doc it says
The ON CONFLICT clause applies to UNIQUE, NOT NULL, CHECK, and PRIMARY KEY constraints. The ON CONFLICT algorithm does not apply to FOREIGN KEY constraints.
Your entity defines a Primary Key but it is tagged with autoGenerate = true. Due to this whenever you create a new User instance and calling insert on your Dao Room is generating a new primary key. This id does not conflict with the "duplicate" User in your db, and hence Room is happy to insert this one without touching the previous "duplicate" version.
The solution is to have the Primary Key from the backend or have a UNIQUE constraint on one field of User entity.

consider looking at this annotation for room :
#Insert(onConflict = OnConflictStrategy.REPLACE)
with this, if you insert anything which already exists, it will simply replace it.
where you are calling :
#Override
public void onChanged(List<User> users) {
adapter.setUserList(users);
}
try clearing the list of the adapter first

anyone looking in the future. The best possible way to do this is to delete the table before inserting new data. e.g.
in your #Dao class define #delete query
#Query("DELETE FROM Users")
void deleteAllUsersData();
and call this query every time before inserting new data into the database. if you want the same primary key then set #Primary key autogenerate to false.

Related

How to update room database based on server response

I am fetching data from server and saving in room database and then from room showing it in recycler view.Data is perfectly saving in room database and showing in recycler view.
Problem: When I am deleting some data from server database then its old copy that saved earlier still persists in room.
What I want: I don't want to show data deleted from server in recycler view.So how can I update room database based on server response.
This is what I have done so far:
UserDao.java
#Dao
public interface UserDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void Insert(User... users);
#Query("SELECT * FROM Users")
LiveData<List<User>> getRoomUsers();
}
User.java
#Entity(tableName = "Users")
public class User {
#NonNull
#PrimaryKey
private String id;
#ColumnInfo(name = "name")
#SerializedName("name")
#Expose
private String name;
#ColumnInfo(name = "age")
#SerializedName("age")
#Expose
private String age;
public User(String id,String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
UserRepository.java
public class UserRepository {
private Context context;
private UserDb userDb;
private LiveData<List<User>> listLiveData;
public UserRepository(Context context) {
this.context = context;
userDb = UserDb.getInstance(context);
listLiveData = userDb.userDao().getRoomUsers();
}
public void getUserList(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<User>> userList = apiService.getUser();
userList.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, final Response<List<User>> response) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
if(response.body() != null) {
List<User> list = response.body();
for (int i = 0; i < list.size(); i++) {
String id = list.get(i).getId();
String names = list.get(i).getName();
String age = list.get(i).getAge();
User user = new User(id,names,age);
userDb.userDao().Insert(user);
}
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Toast.makeText(context,"Data inserted",Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
Toast.makeText(context,e.getMessage(),Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Toast.makeText(context,t.getMessage(),Toast.LENGTH_LONG).show();
}
});
}
public LiveData<List<User>> getRoomUsers(){
return listLiveData;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
UserRepository userRepository;
RecyclerView recyclerView;
UserViewModel userModel;
List<User> userList;
UserAdapter adapter;
ProgressBar prg;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
prg = findViewById(R.id.prg);
userRepository = new UserRepository(this);
userModel = ViewModelProviders.of(this).get(UserViewModel.class);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userList = new ArrayList<>();
adapter = new UserAdapter(userList,this);
recyclerView.setAdapter(adapter);
userModel.getListLiveData().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(List<User> users) {
prg.setVisibility(View.INVISIBLE);
adapter.setUserList(users);
}
});
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(MainActivity.this,AddUser.class);
startActivity(i);
}
});
userRepository.getUserList();
}
Someone please let me know how can I get desired result. Any help would be appreciated.
THANKS
Use getRoomUsers() to get a list of known users from the repository in onResponse().
For each user in response.body(), check if it has a corresponding list item in the known users' list. Remove this item.
Afterwards, the list of known users will contain only users which have been deleted from the server. So for each of these remaining users, you can delete them from the room database.
The following code snippet shows how to find the users which are currently not known to the server:
if (response.body() != null) {
List<User> list = response.body();
List<User> previouslyKnownUsers = new ArrayList<>(userDb.userDao().getRoomUsers().getValue());
for (int i = 0; i < list.size(); i++) {
String id = list.get(i).getId();
String names = list.get(i).getName();
String age = list.get(i).getAge();
User user = new User(id, names, age);
userDb.userDao().Insert(user);
User knownUser = null;
for(User previouslyKnownUser: previouslyKnownUsers){
if(previouslyKnownUser.getId().equals(user.getId())){
knownUser = previouslyKnownUser;
break;
}
}
if(knownUser != null){
previouslyKnownUsers.remove(knownUser);
}
}
for(User currentlyUnknownUser: previouslyKnownUsers){
userDb.userDao().Delete(currentlyUnknownUser);
}
}
In order to make it work, you have to add a method Delete(User user) in UserDao.java

Fetching data using retrofit2 and saving in room

I am using retrofit2 for fetching data from the server and after fetching saving data in room database and then showing in recycler view.Whenever app runs its fetches data from the server and save it in room database.I have successfully fetched JSON data from server and saved in room database and from room it is properly showing in recycler view.
Problem: Whenever data fetches from the server it inserts the same old data in room again due to which same data shows multiple times in recycler view.
What I want: I don't want recycler view to show same data multiple times.I don't want to copy same data again in room database.
This is what I have done so far:
UserDao.java
#Dao
public interface UserDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void Insert(User... users);
#Query("SELECT * FROM Users")
LiveData<List<User>> getRoomUsers();
}
User.java
#Entity(tableName = "Users")
public class User {
#PrimaryKey
private String id;
#ColumnInfo(name = "name")
#SerializedName("name")
#Expose
private String name;
#ColumnInfo(name = "age")
#SerializedName("age")
#Expose
private String age;
public User(String id,String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
UserRepository.java
public class UserRepository {
private Context context;
private UserDb userDb;
private LiveData<List<User>> listLiveData;
public UserRepository(Context context) {
this.context = context;
userDb = UserDb.getInstance(context);
listLiveData = userDb.userDao().getRoomUsers();
}
public void getUserList(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<User>> userList = apiService.getUser();
userList.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, final Response<List<User>> response) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
if(response.body() != null) {
List<User> list = response.body();
for (int i = 0; i < list.size(); i++) {
String names = list.get(i).getName();
String age = list.get(i).getAge();
String id = UUID.randomUUID().toString();
User user = new User(id,names,age);
userDb.userDao().Insert(user);
}
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Toast.makeText(context,"Data inserted",Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
Toast.makeText(context,e.getMessage(),Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Toast.makeText(context,t.getMessage(),Toast.LENGTH_LONG).show();
}
});
}
public LiveData<List<User>> getRoomUsers(){
return listLiveData;
}
}
UserViewModel.java
public class UserViewModel extends AndroidViewModel {
private UserRepository repo;
private LiveData<List<User>> listLiveData;
public UserViewModel(#NonNull Application application) {
super(application);
repo = new UserRepository(application);
listLiveData = repo.getRoomUsers();
}
public LiveData<List<User>> getListLiveData() {
return listLiveData;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
UserRepository userRepository;
RecyclerView recyclerView;
UserViewModel userModel;
List<User> userList;
UserAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
userRepository = new UserRepository(this);
userModel = ViewModelProviders.of(this).get(UserViewModel.class);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userList = new ArrayList<>();
adapter = new UserAdapter(userList,this);
recyclerView.setAdapter(adapter);
userModel.getListLiveData().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(List<User> users) {
adapter.setUserList(users);
}
});
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(MainActivity.this,AddUser.class);
startActivity(i);
}
});
userRepository.getUserList();
}
UserAdapter.java
public class UserAdapter extends
RecyclerView.Adapter<UserAdapter.ViewHolder> {
List<User> userList;
Context context;
public UserAdapter(List<User> userList, Context context) {
this.userList = userList;
this.context = context;
}
#NonNull
#Override
public UserAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_row_layout,parent,false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull UserAdapter.ViewHolder holder, int position) {
User users = userList.get(position);
holder.row_name.setText(users.getName());
holder.row_age.setText(users.getAge());
}
#Override
public int getItemCount() {
return userList.size();
}
public void setUserList(List<User> userList) {
this.userList = userList;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView row_name,row_age;
public ViewHolder(#NonNull View itemView) {
super(itemView);
row_name = itemView.findViewById(R.id.row_name);
row_age = itemView.findViewById(R.id.row_age);
}
}
}
Someone please let me know how can I achieve desired result. Any help would be appreciated.
THANKS
The answer is quite simple, you do not have a unique primary key. You're generating a key yourself using
String id = UUID.randomUUID().toString();
In your first request, you might have this:
User("mdkasdkasjkdjakjdkasd", "Zun", 22);
and in your second request you get
User("djei3ujf493j9fj49dj9", "Zun", 22);
as such, you'll always have duplicate entries in your database since room considers the user with name 'Zun" to NOT be the same.
In order to solve this, create a unique primary key that's unique to a User class. Do not use a random text generator.
Okay you should do it like this,
check if user is exist in db or not,
#Query("SELECT * FROM user WHERE id = :userId")
public User idUserExists(int userId);
if it does than add update query
#Update
public void updateUser(User user); // keep the model with same user id
else insert the new record
#Insert
public void insertUser(User user); // Model with new user Id

How to get value of LiveData from repository that don't access to lifeCycleOwner for observing it?

I have used of MVVM and ROOM and databindig in my app.According
Guide to app architecture ,I want to cash data using room.In the xml layout of RecyclerView item, I use CategoryViewModel variable.I get list of categories from Room database withLiveData type. I want to change LiveData<list<CategoryItem>> type to MutableLiveData<ArrayList<CategoryViewModel>> type. Because finally my adapter consume ArrayList<CategoryViewModel> data type.How to get value of LiveData? When I call getValue() method, returns null.
this is CategoryItem model:
#Entity(tableName = "category_table")
public class CategoryItem implements Serializable {
#PrimaryKey
private int id;
private String title;
private String imagePath;
#TypeConverters({SubCategoryConverter.class})
private ArrayList<String> subCategory;
#TypeConverters({DateConverter.class})
private Date lastRefresh;
public CategoryItem(int id, String title, String imagePath, ArrayList<String> subCategory, Date lastRefresh) {
this.id = id;
this.title = title;
this.imagePath = imagePath;
this.subCategory = subCategory;
this.lastRefresh=lastRefresh;
}
public CategoryItem(int id, String title, String imagePath) {
this.id = id;
this.title = title;
this.imagePath = imagePath;
}
public CategoryItem() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public ArrayList<String> getSubCategory() {
return subCategory;
}
public void setSubCategory(ArrayList<String> subCategory) {
this.subCategory = subCategory;
}
public Date getLastRefresh() {
return lastRefresh;
}
public void setLastRefresh(Date lastRefresh) {
this.lastRefresh = lastRefresh;
}
}
this is CategoryViewModel class:
public class CategoryViewModel extends AndroidViewModel {
private String title;
private String imagePath;
private MutableLiveData<ArrayList<CategoryViewModel>> allCategories=new MutableLiveData<>();
private CategoryRepository repository;
public CategoryViewModel(#NonNull Application application) {
super(application);
repository=new CategoryRepository(application, Executors.newSingleThreadExecutor());
}
public void init(CategoryItem categoryItem){
this.title=categoryItem.getTitle();
this.imagePath=categoryItem.getImagePath();
}
public MutableLiveData<ArrayList<CategoryViewModel>> getAllCategories(){
allCategories=repository.getCategory();
return allCategories;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImagePath() {
return imagePath;
}
}
This is CategoryRepository class:
public class CategoryRepository {
private static final String TAG="CategoryRepository";
private static int FRESH_TIMEOUT_IN_MINUTES = 1;
private final Executor executor;
private APIInterface apiInterface;
public MutableLiveData<ArrayList<CategoryViewModel>> arrayListMutableLiveData=new MutableLiveData<>();
private CategoryDao categoryDao;
private Application application;
public CategoryRepository(Application application,Executor executor) {
this.executor = executor;
this.application = application;
apiInterface= APIClient.getClient().create(APIInterface.class);
LearnDatabase database= LearnDatabase.getInstance(application);
categoryDao=database.categoryDao();
}
public MutableLiveData<ArrayList<CategoryViewModel>> getCategory(){
refreshCategory();
List<CategoryItem> items;
categoryDao.loadCategoryItem();
items=categoryDao.loadCategoryItem().getValue(); // return null
CategoryItem category;
ArrayList<CategoryViewModel> arrayList=new ArrayList<>();
for(int i=0;i<items.size();i++){
category=items.get(i);
CategoryViewModel categoryViewModel=new CategoryViewModel(application);
categoryViewModel.init(category);
arrayList.add(categoryViewModel);
}
arrayListMutableLiveData.setValue(arrayList);
return arrayListMutableLiveData;
}
private void refreshCategory(){
executor.execute(() -> {
String lastRefresh=getMaxRefreshTime(new Date()).toString();
boolean sliderExists =(!(categoryDao.hasCategory(lastRefresh)).isEmpty());
Log.e(TAG,"sliderExist: "+sliderExists);
Log.e(TAG,"lastrefresh: "+lastRefresh);
Log.e(TAG,"hasSlider: "+categoryDao.hasCategory(lastRefresh).toString());
// If user have to be updated
if (!sliderExists) {
Log.e(TAG,"in if");
apiInterface.getCategory().enqueue(new Callback<List<CategoryItem>>() {
#Override
public void onResponse(Call<List<CategoryItem>> call, Response<List<CategoryItem>> response) {
executor.execute(() -> {
List<CategoryItem> categories=response.body();
for (int i=0;i<categories.size();i++){
categories.get(i).setLastRefresh(new Date());
categoryDao.saveCategory(categories.get(i));
}
});
}
#Override
public void onFailure(Call<List<CategoryItem>> call, Throwable t) {
Log.e(TAG,"onFailure "+t.toString());
}
});
}
});
}
private Date getMaxRefreshTime(Date currentDate){
Calendar cal = Calendar.getInstance();
cal.setTime(currentDate);
cal.add(Calendar.MINUTE, -FRESH_TIMEOUT_IN_MINUTES);
return cal.getTime();
}
}
This is xml layout of item of recyclerView:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data class="CategoryDataBinding">
<variable
name="category"
type="com.struct.red.alltolearn.viewmodel.CategoryViewModel"/>
</data>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="200dp"
android:layout_height="150dp"
app:cardCornerRadius="15dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imgItemCategory"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:imageUrl="#{category.imagePath}" />
<TextView
android:id="#+id/txtTitleItemCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="#{category.title}"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</layout>
This is CategoryDao class:
#Dao
public interface CategoryDao {
#Query("SELECT * FROM course_table")
LiveData<List<CategoryItem>> loadCategoryItem();
#Insert(onConflict = OnConflictStrategy.REPLACE)
void saveCategory(CategoryItem category);
#Query("SELECT * FROM category_table WHERE lastRefresh > Date(:lastRefreshMax)")
List<CategoryItem> hasCategory(String lastRefreshMax);
}
And finally I observe MutableLiveData in my Fragment:
private void setupCategoryRecycler() {
categoryViewModel = ViewModelProviders.of(this).get(CategoryViewModel.class);
categoryViewModel.getAllCategories().observe(this, new Observer<ArrayList<CategoryViewModel>>() {
#Override
public void onChanged(#Nullable ArrayList<CategoryViewModel> categoryViewModels) {
Log.e(TAG, "categoryitem: " + categoryViewModels.toString());
categoryAdapter = new CategoryAdapter(getContext(), categoryViewModels);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true);
linearLayoutManager.setReverseLayout(true);
CategoryRecy.setLayoutManager(linearLayoutManager);
CategoryRecy.setAdapter(categoryAdapter);
}
});
}
Your problem is here, right?
public MutableLiveData<ArrayList<CategoryViewModel>> getCategory(){
...
items=categoryDao.loadCategoryItem().getValue(); // returns null
...
}
This is because your categoryDao.loadCategoryItem() method returns LiveData object. It means that method call will be executed in background thread. So when you call getValue() method the value yet null that moment.
To escape from this you can do two bad things.
1. Call loadCategoryItem() earlier, to have values later when calling getValue();
Your Repository class
public class CategoryRepository {
Livedata<List<CategoryItem>> items; // moved here
...
public void init () {
items=categoryDao.loadCategoryItem();
}
public MutableLiveData<ArrayList<CategoryViewModel>> getCategory(){
ArrayList<CategoryViewModel> arrayList=new ArrayList<>();
List<CategoryItem> currentList = items.getValue();
for(int i=0;i<currentList.size();i++){
...
}
arrayListMutableLiveData.setValue(arrayList);
return arrayListMutableLiveData;
}
}
Your ViewModel class
public class CategoryViewModel extends AndroidViewModel {
public void init(CategoryItem categoryItem){
repository.init(); // added
this.title=categoryItem.getTitle();
this.imagePath=categoryItem.getImagePath();
}
This can work but we have 2 problems. First is that still there is no guarantee that values will not be null. Second problem is that you cannot observe your item changes. Even tho you are returning arrayListMutableLiveData object, which is livedata, you are setting its value manually once, and its value will not be changed unless you call getCategory() again.
2. Second hack is load category items synchronously
public interface CategoryDao {
#Query("SELECT * FROM category_table") LiveData<List<CategoryItem>>loadCategoryItem();
#Query("SELECT * FROM category_table") List<CategoryItem> loadCategoryItemsSync();
In this case your getAllCategories () and getCategory() methods also should work synchronously.
Something like this
public void getCategory(Listener listener){
executor.execute(() -> {
ArrayList<CategoryViewModel> arrayList=new ArrayList<>();
List<CategoryItem> currentList = items.getValue();
for(int i=0;i<currentList.size();i++){
...
}
arrayListMutableLiveData.setValue(arrayList);
listener.onItemsLoaded(arrayListMutableLiveData);
}
}
In this case also we have the second problem -> you cannot observe your item changes.
I wrote this to better clarify the problem. *
The real problem is that you trying use CategoryViewModel for data binding.
Please use CategoryItem instead
I suggest to remove this two rows from viewModel
private String title;
private String imagePath;
Try to solve your problem without parsing data from List to ArrayList.
public LiveData<List<CategoryItem>> getAllCategories(){
if (items == null) {
items = categoryDao.loadCategoryItem()
}
return items;
}
then try to use CategoryItem as data object
<data class="CategoryDataBinding">
<variable
name="category"
type="com.struct.red.alltolearn.///.CategoryItem "/>
</data>
and try to change your adapter to make possible doing this
categoryViewModel = ViewModelProviders.of(this).get(CategoryViewModel.class);
categoryViewModel.getAllCategories().observe(this, new Observer<List<CategoryItem >>() {
#Override
public void onChanged(#Nullable List<CategoryItem > categoryItems) {
categoryAdapter = new CategoryAdapter(getContext(), categoryItems);
...
Maybe you can use a trasnformation?
//this is returned to the observer in setupCategoryRecycler()
return Transformations.switchMap(repository.getCategory()) { result ->
//do any other stuff you need here
allCategories.setValue(result)
}
A transformation can be use to convert one liveData into another. Check: https://developer.android.com/topic/libraries/architecture/livedata#transform_livedata
You're trying to load data from the wrong table course_table
#Query("SELECT * FROM course_table") LiveData>
loadCategoryItem();
It should be category_table
Your items=categoryDao.loadCategoryItem().getValue() will not have any value unless you call observe on it.

LiveData is not updating its value after first call

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.

FirebaseUI-Android RecyclerView Not Responding to POJO

My Android FirebaseUI Recycler view is responding well with String class datatype. But is not populating view when I use my POJO class as datatype.
I have taken time to ensure the names in my POJO matches the names in JSON file. Below are my codes:
public class MainActivity extends AppCompatActivity {
public static class MessageViewHolder extends RecyclerView.ViewHolder {
TextView messageTextView;
public MessageViewHolder(View view) {
super(view);
messageTextView =(TextView) messageTextView.findViewById(android.R.id.text1);
}
}
android.support.v7.widget.RecyclerView recyclerView;
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onStart()
{
super.onStart();
//Recycler
recyclerView=(android.support.v7.widget.RecyclerView)findViewById(R.id.recyclerview);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//Adapter
FirebaseRecyclerAdapter<User, MessageViewHolder> mFirebaseAdapter=
new FirebaseRecyclerAdapter<User, MessageViewHolder>(User.class,
R.layout.layout,
MessageViewHolder.class,
myRef.child("User")
) {
#Override
protected void populateViewHolder(MessageViewHolder viewHolder, User model, int position) {
viewHolder.messageTextView.setText(model.getName());
}
};
recyclerView.setAdapter(mFirebaseAdapter);
}
}
POJO:
public class User {
public User(){
}
String Name;
String email;
public User(String Name){
this.Name=Name;
}
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
}
Error From Firebase
Exception com.google.firebase.database.DatabaseException: Can't convert object of type java.lang.String to type com.projectgenuis.eneanews.User
EDIT(JSON)
{
"User" : {
"Name" : "Zion",
}
The FirebaseUI adapters are designed to show lists of items, in your case a list of users. In your current data model, there is only a single user under the /User node. That means there is no way to show it in a list view.
The simplest way to fix this is to change your JSON to have a list of users under a Users node:
{
"Users" : {
"User1": {
"Name" : "Zion"
}
"User2": {
"Name" : "puf"
}
}
}
And then attach the adapter to this collection:
mFirebaseAdapter= new FirebaseRecyclerAdapter<User, MessageViewHolder>(
User.class,
R.layout.layout,
MessageViewHolder.class,
myRef.child("Users")
) {

Categories

Resources