Why does Room database lose data after restarting the app? - java

I'm using Room Persistance library with all Android Architucture Components. In the app I have 3 databases, but the problem is with only one. In my MainActivity I have a RecyclerView that show data (dates) from DatesDatabase. When clicking on each element, a new activity opens and shows all the data that refers to particular date. The query in DAO is:
#Query("SELECT * FROM Sorted WHERE date = :date")
LiveData<List<Sorted>> getSortedWhereDateIs(String date);
Problem is that when I restart the app I still can see the dates, that have been added earlier, but there is no data that refers to this date.
Before restarting:
screenshot1
screenshot2
After restarting:
screenshot1
screenshot2
Code to DatesDatabase:
#Database(entities = {Dates.class}, version = 2, exportSchema = false)
public abstract class DatesDatabase extends RoomDatabase {
private static DatesDatabase instance;
public abstract DatesDao datesDao();
public static synchronized DatesDatabase getInstance(Context context){
if (instance == null){
instance = Room.databaseBuilder(context.getApplicationContext(),
DatesDatabase.class, "dates_database").fallbackToDestructiveMigration()
.build();
}
return instance;
}
}
Code to database, that doesn't save data:
#Database(entities = {Sorted.class}, version = 3, exportSchema = false)
public abstract class SortedDatabase extends RoomDatabase {
private static SortedDatabase instanceSorted;
public abstract SortedDao sortedDao();
public static synchronized SortedDatabase getSortedInstance(Context context) {
if (instanceSorted == null) {
instanceSorted = Room.databaseBuilder(context.getApplicationContext(),
SortedDatabase.class, "unsorted_database").fallbackToDestructiveMigration().build();
}
return instanceSorted;
}
}
I tried to delete "fallbackToDestructiveMigration()", but I have a method "deleteAll", that shows error in this case:
viewModel.deleteAllDates();
viewModel.deleteAllUnsorted();
viewModel.deleteAllSorted();
Here is how I add data to SortedDatabase(that gets deleted):
if (choosedMethod.equals("Eat a frog")) {
for (int i = 0; i < eatAFrogList.size(); i++){
Unsorted unsorted = eatAFrogList.get(i);
String name = unsorted.getName();
String date = unsorted.getDate();
int timeBegin = unsorted.getTimeBegin();
boolean attach = unsorted.isAttach();
int category = unsorted.getCategory();
int duration = unsorted.getDuration();
String categoryChart = unsorted.getCategoryChart();
Sorted sorted = new Sorted(name, timeBegin, duration, category, attach, date,
categoryChart);
viewModel1.insertSorted(sorted);
}
I sort tasks of class Unsorted, stored in UnsortedDatabase, through algorithm and then add it to SortedDatabase.
My adapter to recyclerview that shows sorted data:
public class SortedAdapter extends RecyclerView.Adapter<SortedAdapter.SortedViewHolder> {
private List<Sorted> list = new ArrayList<>();
#NonNull
#Override
public SortedViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.tasks_layout , parent, false);
return new SortedAdapter.SortedViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull SortedViewHolder holder, int position) {
Sorted data = list.get(position);
holder.title.setText(data.getSortedName());
holder.date.setText(data.getSortedDate());
holder.category.setText(String.valueOf(data.getSortedCategory()));
holder.attach.setText(String.valueOf(data.isSortedAttach()));
holder.to.setText(String.valueOf(toTime(data.getSortedDuration() + data.getSortedTimeBegin())));
holder.from.setText(String.valueOf(toTime(data.getSortedTimeBegin())));
}
public void setSortedData(List<Sorted> sortedList){
this.list = sortedList;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return list.size();
}
class SortedViewHolder extends RecyclerView.ViewHolder{
private TextView title;
private TextView date;
private TextView from;
private TextView to;
private TextView category;
private TextView attach;
SortedViewHolder(#NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.tv_title);
date = itemView.findViewById(R.id.tv_date);
from = itemView.findViewById(R.id.tv_from2);
to = itemView.findViewById(R.id.tv_to2);
category = itemView.findViewById(R.id.tv_category);
attach = itemView.findViewById(R.id.tv_attach);
}
}
}
And, finally, activity, where data is shown:
public class ShowSortedActivity extends AppCompatActivity {
SortedViewModel viewModel;
AppPreferenceManager preferenceManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
preferenceManager = new AppPreferenceManager(this);
if (preferenceManager.getDarkModeState()){
setTheme(R.style.Dark);
}
else{
setTheme(R.style.AppTheme);
}
setContentView(R.layout.activity_show_sorted);
final SortedAdapter adapter = new SortedAdapter();
RecyclerView showSorted = findViewById(R.id.show_sorted);
showSorted.setLayoutManager(new LinearLayoutManager(this));
showSorted.setHasFixedSize(true);
showSorted.setAdapter(adapter);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close);
setTitle("Sorted");
Intent intent = getIntent();
String currentDate = intent.getStringExtra("value");
viewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())).get(SortedViewModel.class);
try {
viewModel.getSortedWhereDateIs(currentDate).observe(this, new Observer<List<Sorted>>() {
#Override
public void onChanged(List<Sorted> sorteds) {
adapter.setSortedData(sorteds);
}
});
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Maybe the data isn't deleted, but there is a problem with displaying it? I could't find my mistake... Thanks for any help.

I guess the problem was because of me creating two databases with the same name "unsorted_database". It seems to work now.

Related

RecyclerView does not update ROOM Database although data exists

My Entity class looks like this:
#Entity
public class User {
public int getId() {
return id;
}
#NonNull
public ArrayList<String> getValues(){
return values;
}
#NonNull
public ArrayList<String> getDates(){
return dates;
}
#NonNull
public String getType_counter() {
return type_counter;
}
#NonNull
public String getWhere_counter() {
return where_counter;
}
#PrimaryKey(autoGenerate = true)
public int id;
#ColumnInfo(name = "d")
#TypeConverters({Converters.class})
public ArrayList<String> dates;
#ColumnInfo(name = "value")
#TypeConverters({Converters.class})
public ArrayList<String> values;
#ColumnInfo(name = "type")
public String type_counter;
#ColumnInfo(name = "location")
public String where_counter;
}
I have one RecyclerView which shows only type and location and when you click on any item it goes to another activity where another RecyclerView shows values and dates of this particular User (by Id) as arrays.
My problem is this: user of my app can either create new User object or update it (add value and date to ArrayLists) and when updating RecyclerView is not working properly.
Suppose I have two User objects and my database looks like this:
My fist object has two values and dates but RecyclerView shows only one.
When I add another value and date to the second User object, RecyclerView shows two elements in both objects.
This is how I update values:
private void saveNewValue(String value, String type, String location){
// Getting current date
Date c = Calendar.getInstance().getTime();
SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy", Locale.getDefault());
String formattedDate = df.format(c);
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
// checking if the counter exists
int mList = userViewModel.setTypeLocation(type, location);
if (mList == 0){
//Saving new User
User user = new User();
if (user.values == null){
user.values = new ArrayList<String>();
}
if (user.dates == null){
user.dates = new ArrayList<String>();
}
user.values.add(value);
user.dates.add(formattedDate);
user.type_counter = type;
user.where_counter = location;
userViewModel.insert(user);
Toast.makeText(this, "Successfully saved!", Toast.LENGTH_SHORT).show();
}
else {
//Getting current data
User mUser = userViewModel.getUserWithLocationType(type, location);
ArrayList<String> values_user = mUser.getValues();
ArrayList<String> dates_user = mUser.getDates();
// Updating arrays
values_user.add(value);
dates_user.add(formattedDate);
// Creating new User object with updated values
User updatedUser = new User();
updatedUser.id = mUser.getId();
updatedUser.type_counter = type;
updatedUser.where_counter = location;
updatedUser.values = values_user;
updatedUser.dates = dates_user;
userViewModel.update(updatedUser);
//userAdapter.notifyItemChanged();
Toast.makeText(this, "Successfully updated!", Toast.LENGTH_SHORT).show();
}
}
This is Adapter class for an already described RecyclerView:
public class CalculationAdapter extends RecyclerView.Adapter<CalculationAdapter.CalcViewHolder>{
private LayoutInflater inflater;
private Context mContext;
private ArrayList<String> mValues;
private ArrayList<String> mDates;
private User mUser;
public CalculationAdapter(Context ctx){
inflater = LayoutInflater.from(ctx);
mContext = ctx;
}
public void setUser(User user){
mUser = user;
mValues = user.getValues();
mDates = user.getDates();
notifyDataSetChanged();
Log.i("values", String.valueOf(mValues));
Log.i("dates", String.valueOf(mDates));
}
#NonNull
#Override
public CalculationAdapter.CalcViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.calculations_item, parent, false);
CalculationAdapter.CalcViewHolder holder = new CalculationAdapter.CalcViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(#NonNull CalculationAdapter.CalcViewHolder holder, int position) {
if (mValues != null & mDates != null){
String value = mValues.get(position);
String date = mDates.get(position);
holder.setValuesDates(value, date, position);
}
else{
holder.mValue.setText("No value");
holder.mDate.setText("No date");
}
}
#Override
public int getItemCount() {
if (mValues != null){
return mValues.size();
} else return 0;
}
class CalcViewHolder extends RecyclerView.ViewHolder {
private TextView mValue;
private TextView mDate;
private int mPosition;
public CalcViewHolder(#NonNull View itemView) {
super(itemView);
mValue = itemView.findViewById(R.id.item_value);
mDate = itemView.findViewById(R.id.item_date);
}
public void setValuesDates(String value, String date, int position){
mValue.setText(value);
mDate.setText(date);
mPosition = position;
}
}
}
This is ViewModel class:
public class UserViewModel extends AndroidViewModel {
private String TAG = this.getClass().getSimpleName();
private UserDao userDao;
private AppDatabase appDatabase;
private LiveData<List<User>> mAllUsers;
private LiveData<String[]> mAllLocations;
private LiveData<User> mUser;
private int UserLocationTypeExists;
public UserViewModel(#NonNull Application application) {
super(application);
appDatabase = AppDatabase.getInstance(application);
userDao = appDatabase.userDao();
mAllUsers = userDao.getAllValues();
mAllLocations = userDao.getAllLocations();
}
public void setId(int id){
mUser = userDao.getUserWithId(id);
}
public User getUserWithLocationType(String type, String location){
return userDao.getUserwithLocationType(type, location);
}
public int setTypeLocation(String type, String location){
UserLocationTypeExists = userDao.UserWithTypeLocationExists(type, location);
return UserLocationTypeExists;
}
public void update(User user) {
new UpdateAsyncTask(userDao).execute(user);
}
public void insert(User user){
new InsertAsyncTask(userDao).execute(user);
}
public void delete(User user){
new DeleteAsyncTask(userDao).execute(user);
}
public LiveData<List<User>> getAllUsers(){
return mAllUsers;
}
public LiveData<String[]> getAllLocations() {
return mAllLocations;
}
public LiveData<User> getUserAtId(){
return mUser;
}
private class InsertAsyncTask extends AsyncTask<User, Void, Void> {
UserDao userDao;
public InsertAsyncTask(UserDao userDao) {
this.userDao = userDao;
}
#Override
protected Void doInBackground(User... users) {
userDao.insertValue(users[0]);
return null;
}
}
private class DeleteAsyncTask extends AsyncTask<User, Void, Void> {
UserDao userDao;
public DeleteAsyncTask(UserDao userDao) {
this.userDao = userDao;
}
#Override
protected Void doInBackground(User... users) {
userDao.deleteValue(users[0]);
return null;
}
}
private class UpdateAsyncTask extends AsyncTask<User, Void, Void> {
UserDao userDao;
public UpdateAsyncTask(UserDao userDao) {
this.userDao = userDao;
}
#Override
protected Void doInBackground(User... users) {
userDao.update(users[0]);
return null;
}
}
}
This is the Activity where I have RecyclerView:
public class CalculationActivity extends AppCompatActivity{
private int id_counter;
private UserViewModel userViewModel;
private CalculationAdapter calcAdapter;
private int rate;
private String value1, date1, value2, date2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculation);
Bundle extras = getIntent().getExtras();
if (extras != null) {
id_counter = extras.getInt("id");
}
RecyclerView recyclerView = findViewById(R.id.recyclerView_calc);
calcAdapter = new CalculationAdapter(this);
recyclerView.setAdapter(calcAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.setId(id_counter);
userViewModel.getUserAtId().observe(this, new Observer<User>() {
#Override
public void onChanged(User user) {
calcAdapter.setUser(user);
}
});
}
}
Could somebody please help me figure out why doesn't my RecyclerView update data correctly? Thanks in advance.
I can add more info if necessary.
This is TypeConverters.class:
public class Converters {
#TypeConverter
public static ArrayList<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
return new Gson().fromJson(value, listType);
}
#TypeConverter
public static String fromArrayList(ArrayList<String> list) {
Gson gson = new Gson();
return gson.toJson(list);
}
}
EDIT: I saved different values in the database. Here is how the database looking:
This is Activity1:
When I click on both items they both show value 2222:
Output of SetUser method (when I click on each of the items):
I/values: [2222]
I/dates: [27-Juli-2022]
I/values: [2222]
I/dates: [27-Juli-2022]
Small mistake... I've been sending ID to Activity2 the wrong way. I've been getting it in the onBindViewHolder method which goes over all the elements and which is why I got only values from the last saved element. I needed to get ID in the Holder class in OnClick method. Thanks for all the help anyway.

Updating RecyclerView data slows down the UI of the activity

I am working on a chat application and I am having some problems displaying the chat messages. For storage I'm using a Room database and in order to display the messages I'm using a RecyclerView. The problem is that the activity gets very slow and not so responsive on scrolling through messages.
Here is my code:
ChatActivity.java
public class ChatActivity extends AppCompatActivity {
public static final String TAG = ChatActivity.class.getSimpleName();
public static Contact contact;
public static boolean isContactConnected;
private CircleImageView mContactPicture;
private ImageView mContactConnected;
private TextView mContactName;
private TextView mContactStatus;
private ChatAdapter mChatAdapter;
private RecyclerView mRecyclerView;
private EmojiconEditText mUserMessageInput;
private View rootView;
private ImageView emojiImageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initializeToolbar();
String contactPhone = Objects.requireNonNull(getIntent().getStringExtra("phone"));
contact = MainActivity.db.getContactDao().findByPhone(contactPhone);
if (MainActivity.notificationMessages.get(contact.getId()) != null) {
MainActivity.notificationMessages.put(contact.getId(), new ArrayList<Message>());
}
updateUI(contact);
initializeViews();
initializeRecyclerView();
EmojIconActions emojIconActions = new EmojIconActions(this, rootView, mUserMessageInput, emojiImageView);
emojIconActions.ShowEmojIcon();
emojIconActions.setIconsIds(R.drawable.ic_baseline_keyboard_24, R.drawable.ic_baseline_emoji_emotions_24);
mChatAdapter = new ChatAdapter(this, new ArrayList<Message>());
mRecyclerView.setAdapter(mChatAdapter);
MainActivity.db.getMessageDao().getLiveMessages(contactPhone).observe(this, new Observer<List<Message>>() {
#Override
public void onChanged(List<Message> newMessages) {
mChatAdapter.setMessages(newMessages);
mRecyclerView.scrollToPosition(newMessages.size() - 1);
}
});
}
[...]
private void updateUI(Contact contact) {
mContactName.setText(contact.getName());
if (!contact.isConnected()) {
Date currentTime = Calendar.getInstance().getTime();
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US);
isContactConnected = false;
mContactStatus.setText(
String.format(
"Last seen %s",
DateManager.getLastActiveText(
df.format(currentTime),
contact.getLastActive()
)
)
);
mContactConnected.setVisibility(View.GONE);
Log.d(TAG, "updateUI: initialized contact UI as disconnected");
} else {
mContactStatus.setText(R.string.active_now);
mContactConnected.setVisibility(View.VISIBLE);
isContactConnected = true;
Log.d(TAG, "updateUI: initialized contact UI as connected");
}
if (contact.getPhotoUri() != null) {
Uri imageUri = Uri.parse(contact.getPhotoUri());
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
mContactPicture.setImageBitmap(bitmap);
Log.d(TAG, "updateUI: loaded contact photo from device");
} catch (IOException e) {
Toast.makeText(
ChatActivity.this,
"Failed to load image from device.",
Toast.LENGTH_SHORT
).show();
e.printStackTrace();
}
}
}
[...]
private void initializeRecyclerView() {
mRecyclerView = findViewById(R.id.chat_recycler_view);
RecyclerView.LayoutManager layoutManager =
new LinearLayoutManager(ChatActivity.this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
Log.d(TAG, "initializeRecyclerView: initialized RecyclerView");
}
[...]
}
AppDatabase.java
#Database(entities = {Contact.class, Message.class}, version = 1)
#TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract ContactDao getContactDao();
public abstract MessageDao getMessageDao();
}
MessageDao.java
#Dao
public interface MessageDao {
#Query("SELECT * FROM messages WHERE to_from = :contact ORDER BY timestamp")
List<Message> getMessages(String contact);
#Query("SELECT * FROM messages WHERE to_from = :contact ORDER BY timestamp")
LiveData<List<Message>> getLiveMessages(String contact);
#Query("SELECT * FROM messages WHERE to_from =:contact AND status = 0")
List<Message> getUndeliveredMessages(String contact);
#Query("SELECT * FROM messages WHERE payloadId = :payloadId LIMIT 1")
Message getMessageByPayloadId(long payloadId);
#Query("SELECT * FROM messages WHERE to_from = :contact ORDER BY timestamp DESC LIMIT 1")
Message getLastMessage(String contact);
#Query("SELECT * FROM messages WHERE to_from = :contact ORDER BY timestamp DESC LIMIT 1")
LiveData<Message> getLastLiveMessage(String contact);
#Query("DELETE FROM messages")
void deleteAllMessages();
#Insert
void addMessage(Message message);
#Update
void updateMessage(Message message);
#Delete
void deleteMessage(Message message);
}
ChatAdapter.java
public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<Message> messages;
public ChatAdapter(Context context, List<Message> messages) {
this.mContext = context;
this.messages = messages;
}
#Override
public int getItemViewType(int position) {
Message message = messages.get(position);
int status = message.getStatus();
if (status == Message.RECEIVED) {
return 0;
} else {
return 1;
}
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if (viewType == 0) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_message_item, parent, false);
return new ChatUserViewHolder(itemView);
}
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_message_item2, parent, false);
return new ChatOtherViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Message currentMessage = messages.get(position);
String messageContent = currentMessage.getContent();
Date date = currentMessage.getTimestamp();
SimpleDateFormat ft = new SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US);
Date currentDate = Calendar.getInstance().getTime();
switch (getItemViewType(position)) {
case 0:
ChatUserViewHolder mHolder = (ChatUserViewHolder) holder;
Contact sender = MainActivity.db.getContactDao().findByPhone(currentMessage.getToFrom());
if (sender.getPhotoUri() != null) {
Uri imageUri = Uri.parse(sender.getPhotoUri());
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), imageUri);
mHolder.getSenderProfilePicture().setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
mHolder.getSenderName().setText(sender.getName());
mHolder.getMessageContent().setText(messageContent);
mHolder.getTimestamp().setText(DateManager.getLastActiveText(ft.format(currentDate), ft.format(date)));
break;
case 1:
ChatOtherViewHolder nHolder = (ChatOtherViewHolder) holder;
SharedPreferences sharedPreferences = mContext.getSharedPreferences("LOGIN_DETAILS", MODE_PRIVATE);
String name = sharedPreferences.getString("name", "");
String photoUri = sharedPreferences.getString("photoUri", null);
if (photoUri != null) {
Uri imageUri = Uri.parse(photoUri);
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), imageUri);
nHolder.getSenderProfilePicture().setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
nHolder.getSenderName().setText(name);
nHolder.getMessageContent().setText(messageContent);
nHolder.getTimestamp().setText(DateManager.getLastActiveText(ft.format(currentDate), ft.format(date)));
if (currentMessage.getStatus() == Message.SENT) {
nHolder.getMessageStatus().setImageResource(R.drawable.ic_baseline_done_24);
} else {
nHolder.getMessageStatus().setImageResource(R.drawable.ic_baseline_done_all_24);
}
break;
default:
break;
}
}
#Override
public int getItemCount() {
return messages.size();
}
public void setMessages(List<Message> messages) {
if (this.messages.size() > 0) {
this.messages.clear();
}
this.messages = messages;
notifyDataSetChanged();
}
ChatItemViewHolder.java
class ChatItemViewHolder extends RecyclerView.ViewHolder {
private CircleImageView mUserProfilePic;
private ImageView mUserStatus;
private TextView mUserProfileName;
private EmojiconTextView mLastMessage;
private TextView mTimestamp;
public ChatItemViewHolder(#NonNull View itemView) {
super(itemView);
mUserProfilePic = itemView.findViewById(R.id.contact_image_item);
mUserProfileName = itemView.findViewById(R.id.contact_name_item);
mUserStatus = itemView.findViewById(R.id.status);
mLastMessage = itemView.findViewById(R.id.contact_status_item);
mTimestamp = itemView.findViewById(R.id.timestamp);
}
public CircleImageView getUserProfilePic() {
return mUserProfilePic;
}
public ImageView getUserStatus() {
return mUserStatus;
}
public TextView getUserProfileName() {
return mUserProfileName;
}
public EmojiconTextView getLastMessage() {
return mLastMessage;
}
public TextView getTimestamp() {
return mTimestamp;
}
}
ChatOtherViewHolder.java
class ChatOtherViewHolder extends RecyclerView.ViewHolder {
private CircleImageView mSenderProfilePicture;
private TextView mSenderName;
private EmojiconTextView mMessageContent;
private TextView mTimestamp;
private ImageView mMessageStatus;
public ChatOtherViewHolder(#NonNull View itemView) {
super(itemView);
mSenderProfilePicture = itemView.findViewById(R.id.sender_profile_pic);
mSenderName = itemView.findViewById(R.id.sender_name);
mMessageContent = itemView.findViewById(R.id.message_content);
mTimestamp = itemView.findViewById(R.id.message_timestamp);
mMessageStatus = itemView.findViewById(R.id.message_status);
}
public CircleImageView getSenderProfilePicture() {
return mSenderProfilePicture;
}
public TextView getSenderName() {
return mSenderName;
}
public EmojiconTextView getMessageContent() {
return mMessageContent;
}
public TextView getTimestamp() {
return mTimestamp;
}
public ImageView getMessageStatus() {
return mMessageStatus;
}
}
Clearly the problem comes from the RV. Initially I thought that the observe method running on the UI thread could be causing problems, but I replaced the action in the onChanged method and the UI is very smooth, so the problem occurs only when I try updating the RV items.
What can I do to solve this issue?
There are two big issues I see
Never do database calls in your adapter, database calls are too expensive to use in there.
You are also loading entire bitmaps into memory, use an image loading library like Glide to load images, they handle recycled view and resizing the image to what they need to be along with asynchronous loading

Can I Entity Room data right from RecyclerView and Save it to Room Database?

I have some adapter which use retrofit to get data right from web api and place it to recyclerview
public class NoticeAdapter extends RecyclerView.Adapter<NoticeAdapter.EmployeeViewHolder> {
private Wind wind;
private ArrayList<Notice> dataList;
private Main main;
private Date currentTime = Calendar.getInstance().getTime();
private RecyclerItemClickListener recyclerItemClickListener;
public NoticeAdapter(ArrayList<Notice> dataList, Main main, Wind wind, RecyclerItemClickListener recyclerItemClickListener) {
this.dataList = dataList;
this.main = main;
this.wind = wind;
this.recyclerItemClickListener = recyclerItemClickListener;
}
#Override
public EmployeeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.single_view_row, parent, false);
return new EmployeeViewHolder(view);
}
#Override
public void onBindViewHolder(EmployeeViewHolder holder, #SuppressLint("RecyclerView") final int position) {
if(getAddressMap()!=null){holder.txtNoticeAddress.setText("Loc: "+getAddressMap());}else{holder.txtNoticeAddress.setText("Loc: Unknown location");}
holder.imageIcon.setImageURI(Uri.parse("android.resource://com.locweather/drawable/i"+dataList.get(position).getIcon()));
holder.txtNoticeWind.setText("Wind: "+roundUp(+wind.getSpeed(),1)+"m/s, "+arrow());
holder.txtNoticeTempMain.setText(roundUp(+main.getTemp(),1)+"°C");
holder.txtNoticeWeather.setText(dataList.get(position).getWeather()+" : "+dataList.get(position).getInfo());
holder.txtNoticeTemp.setText("Feels: "+roundUp(+main.getFeelsLike(),1)+"°C ");
holder.txtNoticeTime.setText(currentTime.toString());
holder.txtNoticeHumidity.setText("Humidity: "+main.getHumidity()+"%");
holder.txtNoticePressure.setText("Pressure: "+main.getPressure()+"hPa");
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
recyclerItemClickListener.onItemClick(dataList.get(position));
}
});
}
#Override
public int getItemCount() {
return dataList.size();
}
class EmployeeViewHolder extends RecyclerView.ViewHolder {
ImageView imageIcon;
TextView txtNoticeWeather, txtNoticeTempMain,txtNoticeTemp, txtNoticeHumidity,txtNoticeAddress,txtNoticePressure,txtNoticeWind,txtNoticeTime;
EmployeeViewHolder(View itemView) {
super(itemView);
imageIcon=itemView.findViewById(R.id.image_icon);
txtNoticeTime= itemView.findViewById(R.id.txt_time);
txtNoticeWind= itemView.findViewById(R.id.txt_notice_wind);
txtNoticeAddress= itemView.findViewById(R.id.txt_notice_title);
txtNoticeWeather = itemView.findViewById(R.id.txt_notice_weather);
txtNoticeTemp = itemView.findViewById(R.id.txt_notice_temp);
txtNoticeHumidity = itemView.findViewById(R.id.txt_notice_humidity);
txtNoticePressure = itemView.findViewById(R.id.txt_notice_pressure);
txtNoticeTempMain = itemView.findViewById(R.id.txt_notice_temp_main);
}
}
This is my recyclerview
This works only when network is enabled
The question is how to set this data right from RecyclerView (or other way) to my Room DataBase when network is enabled by Onclick SaveButton to create other recyclerview and set data there, to get it offline later.
I'm trying to create Entity
#Entity
public class WeatherData {
#PrimaryKey(autoGenerate = true)
private long id;
private String address;
private Double windSpeed;
private Integer windDegree;
private String datalistIcon;
private String datalistInfo;
private String datalistWeather;
private Double mainTemp;
private Double mainFeel;
private Integer mainHumidity;
private Integer mainPressure;
private String time;
private Double locLat;
private Double locLon;
public WeatherData(){}
#Ignore
public WeatherData(String address, Double windSpeed, Integer windDegree, String datalistIcon,String datalistInfo,String datalistWeather, Double mainTemp,Double mainFeel,Integer mainHumidity,Integer mainPressure,String time,LatLng currentLocation,Double locLat,Double locLon) {
this.address = address;
this.windSpeed = windSpeed;
this.windDegree = windDegree;
this.datalistIcon=datalistIcon;
this.datalistInfo=datalistInfo;
this.datalistWeather=datalistWeather;
this.mainTemp=mainTemp;
this.mainFeel=mainFeel;
this.mainHumidity=mainHumidity;
this.mainPressure=mainPressure;
this.time=time;
this.locLat=locLat;
this.locLon=locLon;
}
Dao
#Dao
public interface WeatherDataDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void saveAll(List<WeatherData> weathers);
#Insert(onConflict = OnConflictStrategy.REPLACE)
void save(WeatherData weather);
#Update
void update(WeatherData weather);
#Delete
void delete(WeatherData weather);
#Query("SELECT * FROM WeatherData")
LiveData<List<WeatherData>> findAll();
}
and DataBase
#Database(entities = {WeatherData.class}, version = 1)
public abstract class WeatherDatabase extends RoomDatabase {
public static WeatherDatabase INSTANCE;
public abstract WeatherDataDao weatherDao();
private static final Object sLock = new Object();
public static WeatherDatabase getInstance(Context context) {
synchronized (sLock) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
WeatherDatabase.class, "Weathers.db")
.allowMainThreadQueries()
.build();
}
return INSTANCE;
}
}
Which way do I need to create it?
Create an #Entity Notice which is your data type to be stored in your Room DB.
Create a View Model which is attached to your Activity/Fragment where you need to show this list.
Use your ViewModel to store the list from API into your Room DB.
Create a LiveData which observes on the DB and sends the updated list to the other view.
Code for Saving Data in DB. This needs to be run on Background Thread.
public static void saveNoticeList(Context context, List<Notice> noticeList) {
if (context != null && noticeList != null) {
RoomDatabaseCreator.getInstance(context)
.getDatabase()
.noticeDao()
.saveNotices(noticeList);
}
}
// For Saving in background you can use RxJava, I am using a new thread for simplification
backgroundHandler.post(() -> {
saveNoticeList(getActivity(), dataList);
});
ViewModel
public class NoticeViewModel extends AndroidViewModel {
public MutableLiveData<List<Notice>> mNoticesLiveData = new MutableLiveData<>();
private Context mContext;
public NoticeViewModel(final Application application) {
super(application);
mContext = application.getApplicationContext();
mNoticesLiveData = Transformations.switchMap(databaseCreated,
(Function<Boolean, LiveData<List<Notice>>) isDbCreated -> {
if (!Boolean.TRUE.equals(isDbCreated)) { // Not needed here, but watch out for null
//noinspection unchecked
return ABSENT;
} else {
return databaseCreator.getDatabase()
.noticedao()
.getSavedNotices();
}
}
});
public LiveData<List<Notice> getNoticeLiveData() {
return mNoticesLiveData;
}
}
Activity Code where you need to show the saved data
//1. Initialize the viewModel
NoticeViewModel viewModel = ViewModelProviders.of(this).get(NoticeViewModel.class);
//2. Subscribe to the saved notices live-data to get updates in your view
viewModel.getNoticeLiveData().observe(this
list -> {
if (list.isEmpty()) {
return;
}
// Add the list in your adapter
});

How to reload recyclerview row without position of the list?

I have an ArrayList of a custom object. This ArrayList I pass to my RecyclerView Adapter. When an event occurs I need to retrieve the object from the ArrayList that contains a variable with a unique value, make changes to that object and then reload the ViewHolder corresponding to that object. How do I achieve this?
The problem is that I don't have the position of the object within the ArrayList because the positions keep changing dynamically. I just have this unique value corresponding to a object on the basis of which I want to make changes.
I tried doing list.clear() then reassigning objects to the ArrayList with the updated values and then calling adapter.notifyDataSetChanged() but it didn't work.
EDIT:
I have provided the code below of the Activity which contains the recyclerview. The actual code is much larger so I decided to edit it to only show the necessary details regarding this question. Please let me know if I need to add more code.
So, basically what happens here is that IMActivity is bound to a service and passes its context to the service while binding. When an event occurs in the service a database value is changed and updateActivity() is called in the service.
What I want updateActivity() to do is to check the chatMsgList for a ChatMsg object that contains a variable that has a unique value and reload the viewholder corresponding to that ChatMsg object.
Please let me know if I need to clarify further.
public class IMActivity extends AppCompatActivity implements Sertivity {
private RecyclerView recyclerView;
private ImAdapter imAdapter;
private List<ChatMsg> chatMsgList = new ArrayList<>();
EditText etIM;
ImageButton ibSend;
TalkSeeService.TalkSeeBinder talkSeeBinder;
String usernames[]= new String[10];
String otherUserName;
List<Msg> msgList;
DBAct db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindService(new Intent(com.abdralabs.talksee.IMActivity.this, TalkSeeService.class), new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
talkSeeBinder = (TalkSeeService.TalkSeeBinder)service;
talkSeeBinder.setSertivity(com.abdralabs.talksee.IMActivity.this);
}
#Override
public void onServiceDisconnected(ComponentName name) {
talkSeeBinder.setSertivity(null);
talkSeeBinder = null;
}
},BIND_AUTO_CREATE);
setContentView(R.layout.activity_im);
recyclerView = (RecyclerView)findViewById(R.id.rv_im);
imAdapter = new ImAdapter(chatMsgList);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
layoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(imAdapter);
ibSend = (ImageButton)findViewById(R.id.ib_send);
etIM = (EditText)findViewById(R.id.et_im);
ibSend.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//THE TEXT FROM THE EDITTEXT IS TAKEN AND THEN ADDED TO THE DATABASE
updateChatData();
}
});
db = new DBAct(com.abdralabs.talksee.IMActivity.this,otherUserName);
msgList = db.getAllMessages();
db.close();
prepareChatData();
}
private void prepareChatData() {
for (int i=0; i<msgList.size(); i++) {
ChatMsg chatMsg = new ChatMsg(msgList.get(i).getMessage(),
convertToDayDateTime(Long.valueOf(msgList.get(i).getTime())),
msgList.get(i).getOther(),
getReceiptFromDelivered(i));
chatMsgList.add(chatMsg);
}
imAdapter.notifyDataSetChanged();
}
private void updateChatData(){
msgList = db.getAllMessages();
db.close();
int i = msgList.size() - 1;
ChatMsg chatMsg = new ChatMsg(msgList.get(i).getMessage(),
convertToDayDateTime(Long.valueOf(msgList.get(i).getTime())),
msgList.get(i).getOther(),
getReceiptFromDelivered(i));
chatMsgList.add(chatMsg);
imAdapter.notifyDataSetChanged();
recyclerView.smoothScrollToPosition(imAdapter.getItemCount());
}
#Override
public void updateActivity() {
chatMsgList.clear();
prepareChatData();
recyclerView.smoothScrollToPosition(imAdapter.getItemCount());
}
#Override
public void callActivityMethod() {
updateChatData();
}
private String convertToDayDateTime(long timestamp){
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone();
SimpleDateFormat sdf = new SimpleDateFormat("EEE dd/MM/yyyy HH:mm");
sdf.setTimeZone(tz);
String localTime = sdf.format(new Date(timestamp));
return localTime;
}
private String getReceiptFromDelivered(int position){
String receipt;
if (msgList.get(position).getDelivered().equals("0")){
receipt = "...";
}else {
receipt = "S";
}
return receipt;
}
}
IMAdapter.java
public class ImAdapter extends RecyclerView.Adapter <RecyclerView.ViewHolder> {
private List<ChatMsg> chatMsgList = new ArrayList<ChatMsg>();
final int VIEW_TYPE_USER = 1;
final int VIEW_TYPE_OTHER = 0;
public ImAdapter(List<ChatMsg> chatMsgList){
this.chatMsgList = chatMsgList;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType==VIEW_TYPE_USER){
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_msg_user,parent,false);
return new ImUserViewHolder(itemView);
}
else {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_msg_other,parent,false);
return new ImOtherViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType()==VIEW_TYPE_USER){
ChatMsg chatMsg = chatMsgList.get(position);
ImUserViewHolder imUserViewHolder = (ImUserViewHolder)holder;
imUserViewHolder.msg.setText(chatMsg.getMsg());
imUserViewHolder.timeStamp.setText(chatMsg.getTime());
}
else {
ChatMsg chatMsg = chatMsgList.get(position);
ImOtherViewHolder imOtherViewHolder = (ImOtherViewHolder) holder;
imOtherViewHolder.msg.setText(chatMsg.getMsg());
imOtherViewHolder.timeStamp.setText(chatMsg.getTime());
}
}
#Override
public int getItemViewType(int position) {
ChatMsg chatMsg = chatMsgList.get(position);
if (chatMsg.getOther().equals("true")){
return VIEW_TYPE_OTHER;
}
else {
return VIEW_TYPE_USER;
}
}
#Override
public int getItemCount() {
return chatMsgList.size();
}
public class ImOtherViewHolder extends RecyclerView.ViewHolder{
public TextView msg;
public TextView timeStamp;
public ImOtherViewHolder(View itemView) {
super(itemView);
msg = (TextView)itemView.findViewById(R.id.tv_chat_msg_other);
timeStamp = (TextView)itemView.findViewById(R.id.tv_time_chat_msg_other);
}
}
public class ImUserViewHolder extends RecyclerView.ViewHolder{
public TextView msg;
public TextView timeStamp;
public ImUserViewHolder(View itemView) {
super(itemView);
msg = (TextView)itemView.findViewById(R.id.tv_chat_msg_user);
timeStamp = (TextView)itemView.findViewById(R.id.tv_time_chat_msg_user);
}
}
}
To you IMAdapter add method
public void setChatMsgList(List<ChatMsg> chatMsgList ) {
this.chatMsgList = chatMsgList;
}
and in your IMActivity in method prepareChatData call this new setChatMsgList method
{
...
chatMsgList.add(chatMsg);
}
imAdapter.setChatMsgList(chatMsgList);
imAdapter.notifyDataSetChanged();

Loading images in listview asynchronously with callback

I'm using Parse in my app, and in order to load my 'profile' images, I need to retrieve a so called Parsefile. When the Parsefile is downloaded it uses a callback to notify when it's done. Now this is generally a nice way to do things but I encountered a problem with this when using a Listview and downloading the images with an Asynctask.
The problem is as follows:
In my ListView adapter in the getView method, I create an AsyncTask and execute it, this AsyncTask starts the retrieveProfileImage(callBack) function. In my callback I simply start a Runnable on the UI thread to update the ImageView in the View with the new (retrieved Image). The problem however as it seems, is the fact that as soon as I start my AsyncTask, the View is returned. So I can't set the other images to the correct row. I hope my code demonstrates my problem more clearly.
The ListAdapter:
public class FriendListAdapter extends ArrayAdapter<Profile> {
private int resource;
private Context context;
private List<Profile> friends;
private Profile fProfile;
private Bitmap profileImageBitmap;
private ProgressBar friendImageProgressBar;
//ui
private ImageView friendImage;
public FriendListAdapter(Context context, int resource,
List<Profile> objects) {
super(context, resource, objects);
this.context = context;
this.resource = resource;
this.friends = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView friendName = null;
friendImage = null;
View rowView = convertView;
if (rowView == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
rowView = inflater.inflate(R.layout.friendslist_row, null);
friendName = (TextView) rowView.findViewById(R.id.fName);
friendImage = (ImageView) rowView
.findViewById(R.id.fImage);
friendImageProgressBar = (ProgressBar) rowView.findViewById(R.id.friendImageProgressBar);
} else {
friendName = (TextView) convertView.findViewById(R.id.fName);
friendImage = (ImageView) convertView.findViewById(R.id.fImage);
friendImageProgressBar = (ProgressBar) convertView.findViewById(R.id.friendImageProgressBar);
}
fProfile = friends.get(position);
DownloadProfileImage dImg = new DownloadProfileImage();
dImg.execute();
friendName.setText(fProfile.getName());
return rowView;
}
private class DownloadProfileImage extends AsyncTask<Void, Integer, String> {
#Override
protected String doInBackground(Void... arg0) {
Log.d("logpp", "Starting download image for " + fProfile.getName());
fProfile.retrieveProfileImage(new ProfileImageCallback());
return null;
}
}
private class ProfileImageCallback extends GetDataCallback {
#Override
public void done(byte[] bytearray, ParseException e) {
if (e == null) {
Log.d("logpp", "Done downloading image for " + fProfile.getName() + ". Setting bitmap to:" +
" " + friendImage.getId());
profileImageBitmap = BitmapManager
.getBitmapFromByteArray(bytearray);
((Activity) context).runOnUiThread(new UpdateUi());
}
}
}
private class UpdateUi implements Runnable {
#Override
public void run() {
friendImage.setImageBitmap(profileImageBitmap);
friendImage.setVisibility(View.VISIBLE);
friendImageProgressBar.setVisibility(View.INVISIBLE);
}
}
}
The retrieveProfileImage method:
public void retrieveProfileImage(GetDataCallback callBack) {
this.image.getDataInBackground(callBack);
}
I hope someone can help me with this one.
Regards,
Tim
i solved this problem by following code
public View getView(int position, View convertView, ViewGroup parent) {
try {
if (inflater == null)
inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null)
convertView = inflater.inflate(R.layout.answer_item, null);
TextView name = (TextView) convertView.findViewById(R.id.textView_ans_user_name);
TextView body = (TextView) convertView.findViewById(R.id.textView_ans_user_body);
TextView timestamp = (TextView) convertView.findViewById(R.id.textView_ans_user_timestamp);
final CircularImageView thumbnail = (CircularImageView) convertView.findViewById(R.id.imageView_ans_user);
Parse_answer_model ans = answers.get(position);
name.setText(ans.getAns_by());
body.setText(ans.getAns_body());
SimpleDateFormat sdfAmerica = new SimpleDateFormat("dd-M-yyyy hh:mm:ss a");
sdfAmerica.setTimeZone(TimeZone.getDefault());
String sDateInAmerica = sdfAmerica.format(ans.getCreatedAt());
timestamp.setText(sDateInAmerica);
ParseQuery<User> query = ParseQuery.getQuery("_User");
query.whereEqualTo("username", ans.getAns_by());
query.getFirstInBackground(new GetCallback<User>() {
public void done(User user, ParseException e) {
// TODO Auto-generated method stub
if (e == null) {
img.DisplayImage(user.getprofile_pic_url(), thumbnail, false);
} else {
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
put your imageview as final dont make it global and you get image url from geturl() method, it is as defined by parse you can use below example
ParseFile fileObject = (ParseFile) object.get("image_file");
User user = new User();
user = (User) ParseUser.getCurrentUser();
user.setProfile_pic_url(fileObject.getUrl().toString());
user.saveInBackground();
update
last day i found new solution you can get user's data which related to parse object by following code and made some changes in model class,too.
void getchats() {
pd.show();
ParseQuery<Parse_chat_dialogs> query = ParseQuery.getQuery("chat_dialogs");
query.addDescendingOrder("updatedAt");
query.whereContains("users", ParseUser.getCurrentUser().getUsername());
query.findInBackground(new FindCallback<Parse_chat_dialogs>() {
public void done(List<Parse_chat_dialogs> dilogs, ParseException e) {
if (e == null) {
pd.hide();
dialoglist = (ArrayList<Parse_chat_dialogs>) dilogs;
adp = new ChatDialogAdapter(Chat_list.this, dialoglist);
list.setAdapter(adp);
for (int i = 0; i < dialoglist.size(); i++) {
ParseQuery<User> query = ParseQuery.getQuery("_User");
query.whereEqualTo("username", dialoglist.get(i).getUsers().trim()
.replace(ParseUser.getCurrentUser().getUsername(), "").replace(",", ""));
User user = new User();
try {
user = query.getFirst();
dialoglist.get(i).setFirstname(user.getFirstname());
dialoglist.get(i).setLastname(user.getLastname());
dialoglist.get(i).setProfileurl(user.getprofile_pic_url());
adp.notifyDataSetChanged();
} catch (ParseException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
} else {
Toast.makeText(Chat_list.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
});
}
as in above example i added three new param in parseobejct model class for storing values of user's firstname ,lastname and profile url.
i am also sharing model class for getting more idea
#ParseClassName("chat_dialogs")
public class Parse_chat_dialogs extends ParseObject {
private String firstname;
private String lastname;
private String profileurl;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getProfileurl() {
return profileurl;
}
public void setProfileurl(String profileurl) {
this.profileurl = profileurl;
}
/////////////////////////////////////////////////////////////////////////////
public String getLast_message() {
return getString("last_message");
}
public void setLast_message(String value) {
put("last_message", value);
}
public String getUsers() {
return getString("users");
}
public void setUsers(String value) {
put("users", value);
}
}
How about this!
Instead of using AsyncTask in the adapter class, use it in the MainActivity where you set the adapter for the listview. And in your done method in the Callback or the postExecute update the object/objects and call notifyDataSetChanged().
So, essentially you could have an update method in your adapter class, say, like this,
public void updateObject(int pos, byte[] byteArray){
//Assuming your Profile Object has some member to store this image data
friends.get(pos).setImageData(byteArray); //friends is the list in adapter class and setImageData may be the setter in your Profile object class
notifyDataSetChanged();
}
and in the getView(), you could do something like this
profileImageBitmap = BitmapManager
.getBitmapFromByteArray(friends.get(pos).getImageData);
friendImage.setImageBitmap(profileImageBitmap);

Categories