1

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

Digvijay
  • 2,887
  • 3
  • 36
  • 86
  • Use `@Update` instead of `@Insert` – Santanu Sur Jul 26 '19 at 12:42
  • What all I need to change in code please let me know. – Digvijay Jul 26 '19 at 12:45
  • Possible duplicate of [Update some specific field of an entity in android Room](https://stackoverflow.com/questions/45789325/update-some-specific-field-of-an-entity-in-android-room). Check Jurij Pitulja 's answer – Santanu Sur Jul 26 '19 at 12:46
  • Before insert data in room database just delete that data with _id_ after making method in your dao. – Piyush Jul 26 '19 at 12:47
  • @Digvijay before adding new data into room check if current data exist or not if yes then update data if not then make new entry – Om Infowave Developers Jul 26 '19 at 12:50
  • If your userId(primary key) is same in the model then using @update will automatically update the whole row, so you can query and check if the id exist than call **@Update** else call **@Insert** – Hardik Chauhan Jul 26 '19 at 12:51
  • Also you can query for DISTINCT value only based on your userid to make sure you have all unique record only. – Hardik Chauhan Jul 26 '19 at 12:55
  • @Om Infowave developers, please help me with code how can I implement it. – Digvijay Jul 26 '19 at 12:56
  • @Hardik chauhan, could you please post code. – Digvijay Jul 26 '19 at 12:58
  • @Hardik chauhan , but I have to fetch new data also along with old data whenever new data added on server so how can I implement both update and insert simultaneously. – Digvijay Jul 26 '19 at 13:03
  • Share your `UserAdapter` please – Zun Jul 26 '19 at 13:14
  • @Digvijay can you add your user adapter code, because if userIds are same than room will not create a new row since you already added OnConflictStrategy.REPLACE. I think the issue could be on your adapter side – Pavan Varma Jul 26 '19 at 13:14
  • @Pavan Varma, I have updated my post with UserAdapter class.PLease take a look. – Digvijay Jul 26 '19 at 13:18

2 Answers2

2

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.

Zun
  • 1,553
  • 3
  • 15
  • 26
-1

Okay you should do it like this,

  1. check if user is exist in db or not,
@Query("SELECT * FROM user WHERE id = :userId")
public User idUserExists(int userId);
  1. if it does than add update query
@Update
public void updateUser(User user); // keep the model with same user id
  1. else insert the new record
@Insert 
public void insertUser(User user); // Model with new user Id
Hardik Chauhan
  • 2,750
  • 15
  • 30
  • Your first query makes no sense. Due to the WHERE clause, only 1 user will be returned. The function name `loadAllUsersOlderThan` is VERY misleading. Ask yourself: why does your function return `User[]`. This answer is incorrect. – Zun Jul 26 '19 at 13:09
  • Also, this answer doesn't answer the main issue, so if I could I would downvote this answer twice. 1) it doesn't answer the question 2) the given answer doesn't make sense. – Zun Jul 26 '19 at 13:12
  • `SELECT * FROM user WHERE id = :userId` this is an inefficient way of checking if a record exists. Please update by following best practices. – Zun Jul 26 '19 at 13:13
  • it was just a method name cant you check for method name by yourself atleast. – Hardik Chauhan Jul 26 '19 at 13:14
  • Okay fine, no problem I wont be creating a whole program this is just a template of way to solve the problem. That first you check for userID If exist than update that if not than insert. That's the whole idea – Hardik Chauhan Jul 26 '19 at 13:15
  • if you want the whole code or example you can surely google that out. – Hardik Chauhan Jul 26 '19 at 13:17
  • This is inefficient code and thus not good enough for a "template". If you think I'm wrong then prove me wrong by providing best practices for Room and SQL, and a memory profiler showing that this is efficient. – Zun Jul 26 '19 at 13:20