I populate a RecyclerView from JSON data pulled from a webserver using Volley. I have two columns called user1Read and user2Read that is yes or no. I would like to know how I can modify my Adapter to set the TypeFace to bold if the message is considered "unread"? I am unsure of how to access the TextView from within my onResponse logic. Should I create a member variable in my Messages model class? Should I create it in my Adapter? Where would be the best place to put this? In my JSON onResponse, it would be great if I could just say
onResponseMethod {
if(!hasRead) {
//set TypeFace BOLD here
//But how to access TextView???
}
}
My custom Adapter:
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import de.hdodenhof.circleimageview.CircleImageView;
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder> implements Filterable {
private ArrayList<Message> messages;
private OnItemClickListener mListener;
@Override
public Filter getFilter() {
return null;
}
public interface OnItemClickListener {
void onItemClick(int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
public MessageAdapter(ArrayList<Message> messages) {
this.messages = messages;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_list_item, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Message message = messages.get(position);
holder.username.setText(message.getUsername());
holder.messageSnippet.setText(message.getMessageSnippet());
holder.singleMessageTimestamp.setText(message.getTimestamp());
Picasso.get().load(message.getAvatarUrl()).into(holder.avatarCircleImageView);
}
@Override
public int getItemCount() {
if (messages != null) {
return messages.size();
} else {
return 0;
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View view;
public final TextView username;
public final TextView messageSnippet;
public final TextView singleMessageTimestamp;
public final CircleImageView avatarCircleImageView;
public ViewHolder(View view) {
super(view);
this.view = view;
username = view.findViewById(R.id.message_item_username_textview);
messageSnippet = view.findViewById(R.id.message_item_message_snippet_textview);
singleMessageTimestamp = view.findViewById(R.id.message_item_timestamp);
avatarCircleImageView = view.findViewById(R.id.message_item_circle_avatar);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
mListener.onItemClick(position);
}
}
}
});
}
}
}
My Message Model:
import android.widget.ImageView;
public class Message {
private String username;
private String messageSnippet;
private String avatarUrl;
private String timestamp;
private ImageView dot;
public Message(String username, String message, String avatar, String timestamp) {
this.username = username;
this.messageSnippet = message;
this.avatarUrl = avatar;
this.timestamp = timestamp;
}
public Message(String username, String avatar) {
this.username = username;
this.avatarUrl = avatar;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getMessageSnippet() {
return messageSnippet;
}
public void setMessageSnippet(String messageSnippet) {
this.messageSnippet = messageSnippet;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
}
My main Activity (MessagesActivity).
public class MessagesActivity extends AppCompatActivity implements MessageAdapter.OnItemClickListener {
private RecyclerView messages;
private MessageAdapter adapter;
private ImageView circle_avatar;
private TextView header_username_text;
private ImageView avatar;
private TextView username_heading;
private static JSONArray profile_array;
private static final String TAG = "MessagesActivity";
private static final String getMessagesUrl = "https://myURL.com/get_messages.php";
private static String username;
private static ProgressDialog messagesProgressDialog;
private static ArrayList<Message> list = new ArrayList<>();
private static JSONObject jsonObject;
private static Timer updateTimer;
private static String hasRead;
private static RecyclerView.LayoutManager mLayoutManager;
private DrawerLayout mDrawerLayout;
private ImageButton composeImageButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messages);
configureNavigationDrawer();
configureToolbar();
if(list != null) {
list.clear();
}
avatar = findViewById(R.id.profile_avatar);
username_heading = findViewById(R.id.username_text);
updateTimer = new Timer();
TimerTask updateTimerTask = new TimerTask() {
public void run() {
refreshMessages();
}
};
updateTimer.scheduleAtFixedRate(updateTimerTask, 3000, 3000);
messagesProgressDialog = new ProgressDialog(MessagesActivity.this, R.style.Custom_Progress_Dialog);
messagesProgressDialog.setIndeterminate(true);
messagesProgressDialog.setMessage("Fetching Messages...");
messagesProgressDialog.show();
SharedPreferences sharedPreferences = this.getSharedPreferences("MyPref", MODE_PRIVATE);
username = sharedPreferences.getString("username", null);
getProfile(username);
messages = findViewById(R.id.messages);
messages.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
ArrayList<Message> messages = initMessages();
mLayoutManager = new LinearLayoutManager(this);
this.messages.setLayoutManager(mLayoutManager);
adapter = new MessageAdapter(messages);
this.messages.setAdapter(adapter);
adapter.setOnItemClickListener(this);
getProfile(username);
composeImageButton = findViewById(R.id.custom_toolbar_image_button);
composeImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Clicked compose Image Button");
Intent composeIntent = new Intent(MessagesActivity.this, ComposeActivity.class);
startActivity(composeIntent);
updateTimer.cancel();
updateTimer.purge();
}
});
}
private ArrayList<Message> initMessages() {
RequestQueue messagesQueue = Volley.newRequestQueue(this);
StringRequest messagesRequest = new StringRequest(Request.Method.POST, getMessagesUrl, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
messagesProgressDialog.dismiss();
try {
String baseUrl = "https://myURL.com";
String defaultbaseUrl = "https://myURL.com/css/images/user_default/default_avatar.png";
JSONObject responseObject = new JSONObject(response);
JSONArray responseArray = responseObject.getJSONArray("data");
for(int i = 0; i < responseArray.length(); i++) {
jsonObject = responseArray.getJSONObject(i);
hasRead = jsonObject.getString("has_read");
String avatar = jsonObject.getString("avatar");
String message = jsonObject.getString("message");
String sender = jsonObject.getString("sender");
String timestamp = jsonObject.getString("timestamp");
Log.d(TAG, "sender: "+sender+", hasRead: "+hasRead);
if(avatar.contains("../users")) {
String substring = avatar.substring(avatar.indexOf(".") + 2);
avatar = baseUrl+substring;
Log.d(TAG, "if(avatar.contains('../users'): "+avatar);
} else {
avatar = defaultbaseUrl;
}
list.add(new Message(sender, message, avatar, timestamp));
}
updateData();
} catch(Exception e) {
Log.d(TAG, "EXCEPTION: "+e.getMessage());
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "VolleyError: "+error.getMessage());
messagesProgressDialog.dismiss();
}
}) {
@Override
public Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("username", username);
return params;
}
};
messagesQueue.add(messagesRequest);
return list;
}
/*
Called with TimerTask
*/
private void refreshMessages() {
RequestQueue updateQueue = Volley.newRequestQueue(this);
StringRequest updateRequest = new StringRequest(Request.Method.POST, getMessagesUrl, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
list.clear();
try {
String baseUrl = "https://myURL.com";
String defaultbaseUrl = "https://myURL.com/css/images/user_default/default_avatar.png";
JSONObject updateObject = new JSONObject(response);
JSONArray updateResponseArray = updateObject.getJSONArray("data");
for (int i = 0; i < updateResponseArray.length(); i++) {
updateObject = updateResponseArray.getJSONObject(i);
hasRead = updateObject.getString("has_read");
String avatar = updateObject.getString("avatar");
String message = updateObject.getString("message");
String sender = updateObject.getString("sender");
String timestamp = updateObject.getString("timestamp");
if (avatar.contains("../users")) {
String substring = avatar.substring(avatar.indexOf(".") + 2);
avatar = baseUrl + substring;
} else {
avatar = defaultbaseUrl;
}
if(hasRead.equals("no")) {
}
list.add(new Message(sender, message, avatar, timestamp));
}
updateData();
adapter.notifyDataSetChanged();
} catch(Exception e) {
Log.d(TAG, "EXCEPTION: "+e.getMessage());
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "VolleyError: "+error.getMessage());
}
}) {
@Override
public Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("username", username);
return params;
}
};
updateQueue.add(updateRequest);
}
@Override
public void onBackPressed() {
super.onBackPressed();
list.clear();
updateTimer.cancel();
updateTimer.purge();
SharedPreferences prefs = MessagesActivity.this.getSharedPreferences("MyPref", MODE_PRIVATE);
prefs.edit().putBoolean("is_logged_in", false).apply();
finish();
}
@Override
protected void onResume() {
super.onResume();
}
public void updateData() {
ArrayList<Message> strs = new ArrayList<>();
for(int i = 0; i < list.size(); i++) {
strs.add(list.get(i));
MessageAdapter updateAdapter = new MessageAdapter(list);
messages = findViewById(R.id.messages);
messages.setAdapter(updateAdapter);
updateAdapter.setOnItemClickListener(this);
}
}
@Override
public void onItemClick(int position) {
Log.d(TAG, "onItemClick Called");
Intent conversationIntent = new Intent(MessagesActivity.this, ConversationActivity.class);
String recipient = ((TextView) Objects.requireNonNull(messages.findViewHolderForAdapterPosition(position)).itemView.findViewById(R.id.message_item_username_textview)).getText().toString();
conversationIntent.putExtra("recipient", recipient);
conversationIntent.putExtra("username", username);
updateTimer.cancel();
updateTimer.purge();
startActivity(conversationIntent);
list.clear();
finish();
}
public void getProfile(final String username) {
final String username_url = "https://myURL.com/get_profile.php?username=" + username;
RequestQueue queue = Volley.newRequestQueue(this);
StringRequest postRequest = new StringRequest(Request.Method.GET, username_url,
new com.android.volley.Response.Listener<String>() {
@Override
public void onResponse(String response) {
String baseUrl = "https://myURL.com";
String defaultbaseUrl = "https://myURL.com/css/images/user_default/default_avatar.png";
try {
profile_array = new JSONArray(response);
for(int i = 0; i < profile_array.length(); i++) {
Log.d(TAG, "getProfile inside for loop");
JSONObject jsonObject = profile_array.getJSONObject(i);
String name = jsonObject.getString("name");
String avatarString = jsonObject.getString("avatar");
Log.d(TAG, "getProfile avatarString: "+avatarString);
if(avatarString.contains("../users")) {
String substring = avatarString.substring(avatarString.indexOf(".") + 2);
Log.d(TAG, "substring: "+substring);
String urlString = baseUrl +substring;
Picasso.get().load(urlString).into(circle_avatar);
Log.d(TAG, "urlString: "+urlString);
//Log.d(TAG, "CircleImageView getDrawable(): "+avatar.getDrawable());
header_username_text.setText(name);
} else {
Picasso.get().load(defaultbaseUrl).into(circle_avatar);
header_username_text.setText(name);
}
}
} catch (JSONException e) {
ToastMaker.createLongToast(getApplicationContext(), "JSONException getProfile: "+ e.getMessage());
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// error
Log.d(TAG, "VolleyError: "+error.getMessage());
//Toast.makeText(getApplicationContext(), TAG + " " + error, Toast.LENGTH_LONG).show();
}
}
) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
return params;
}
};
queue.add(postRequest);
}
public void showLogoutDialog() {
AlertDialog.Builder logoutBuilder = new AlertDialog.Builder(this, R.style.Custom_Alert_Dialog);
logoutBuilder.setIcon(R.mipmap.ic_launcher_foreground);
logoutBuilder.setTitle("Confirm Logout");
logoutBuilder.setMessage("You are about to logout.\nAre you sure?")
.setCancelable(true)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = MessagesActivity.this.getSharedPreferences("MyPref", MODE_PRIVATE);
prefs.edit().putBoolean("is_logged_in", false).apply();
Intent logoutIntent = new Intent(MessagesActivity.this, LoginActivity.class);
startActivity(logoutIntent);
finish();
}
}).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog alertDialog = logoutBuilder.create();
alertDialog.show();
}
private void configureNavigationDrawer() {
mDrawerLayout = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.navigation_view);
circle_avatar = navigationView.getHeaderView(0).findViewById(R.id.profile_circle_avatar);
header_username_text = navigationView.getHeaderView(0).findViewById(R.id.header_username_text);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.edit_profile:
//String name = username_heading.getText().toString();
//String pic = avatar.getDrawable().toString();
//String location = location_heading.getText().toString();
//String interests = interests_heading.getText().toString();
//editProfile(name, pic, location, interests);
Log.d(TAG, "Clicked Edit Profile");
break;
case R.id.messages:
Log.d(TAG, "Clicked Messages");
mDrawerLayout.closeDrawers();
break;
case R.id.privacy:
Intent privacyIntent = new Intent(MessagesActivity.this, PrivacyActivity.class);
startActivity(privacyIntent);
Log.d(TAG, "Clicked Privacy");
break;
case R.id.logout:
showLogoutDialog();
Log.d(TAG, "Clicked Logout Button");
break;
}
mDrawerLayout.closeDrawers();
return false;
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch(itemId) {
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return true;
}
private void configureToolbar() {
Toolbar toolbar = findViewById(R.id.custom_toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null : Log.d(TAG, "actionBar is NULL");
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setHomeAsUpIndicator(R.drawable.ic_action_menu_24dp);
actionBar.setDisplayHomeAsUpEnabled(true);
}
@Override
protected void onRestart() {
super.onRestart();
updateTimer = new Timer();
TimerTask updateTimerTask = new TimerTask() {
public void run() {
refreshMessages();
}
};
updateTimer.scheduleAtFixedRate(updateTimerTask, 3000, 3000);
}
}
Here is the array that is being sent from the webserver:
$data[] = array('has_read' => 'no', 'avatar' => $avatar, 'sender' => $sender, 'recipient' => $recipient, 'message' => $message, 'timestamp' => $time);
$response = array('data'=>$data, 'count'=>$count);
echo json_encode($response);
Update 2 Guess what? It was a typo in another php function not mentioned in my question called
getConversation(). I stopped working on the file a while back to perfect my MessagesActivity. I logged any Exceptions on the Activity, and noticedException in getMessages(): No value for timestamp.I had misspelled my timestamp variable. Lol. So, I have a fully functioning messaging system now! :-) Now to work on some notification bells and whistles. Have a great weekend.
Update: This is not completely solved. It's bold when it's initially received on the MessagesActivity screen, but it goes back to regular
Typefaceonce they refresh (via theTimerTask). Will debug for a while and come back, unless one of you discovers it first. :)I think I figured it out!
My table is like so:
user1, user1ID, user2, user2ID, message, timestamp, user1read, user2readWhen I make my request for a message, I receive these items via the php script:
This was my original loop to get all the messages from my JSONObject:
So, I thought, "why don't I just create another member variable for my Messages model, and call it "hasRead"? So I did just that:
Then I make my getters and setters (leaving out for the sake of brevity).
@J7bits suggested I access the
onBindViewHolder()in my custom Adapter class. So I did that, but I included logic to check the status of the new member variable "hasRead":Then, finally, I changed my JSON
onResponsemethod to include thehas_readvariable sent from php:String hasRead = jsonObject.getString("has_read");And I added it to my
ArrayList<Message> list:list.add(new Message(sender, message, avatar, timestamp, **hasRead**);I entered a new message manually into the database, and set my status to "no" for
user2read. And would you just look at it?!?! :D