Fetch conversation messages remotely

This commit is contained in:
Pierre 2017-12-17 17:13:13 +01:00
parent e30b3ea988
commit 6be0d241c3
6 changed files with 640 additions and 6 deletions

View File

@ -55,7 +55,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -1,5 +1,6 @@
package org.communiquons.android.comunic.client.data.conversations;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Objects;
@ -46,7 +47,7 @@ public class ConversationMessage {
*
* @param conversation_id The ID of the conversation
*/
public void setConversation_id(int conversation_id) {
void setConversation_id(int conversation_id) {
this.conversation_id = conversation_id;
}
@ -64,7 +65,7 @@ public class ConversationMessage {
*
* @param user_id The ID of the user
*/
public void setUser_id(int user_id) {
void setUser_id(int user_id) {
this.user_id = user_id;
}
@ -99,6 +100,19 @@ public class ConversationMessage {
return image_path;
}
/**
* Get the path of the image associated with the content
*
* Warning ! if no image path were specified, "null" will be returned, but as a string instead
* of an empty pointer
*
* @return The path of the image
*/
@NonNull
public String getImagePathNotNull() {
return image_path != null ? image_path : "null";
}
/**
* Set the content of the message
*
@ -122,7 +136,7 @@ public class ConversationMessage {
*
* @param time_insert The time of insertion of the message
*/
public void setTime_insert(int time_insert) {
void setTime_insert(int time_insert) {
this.time_insert = time_insert;
}

View File

@ -0,0 +1,228 @@
package org.communiquons.android.comunic.client.data.conversations;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.communiquons.android.comunic.client.data.DatabaseHelper;
import org.communiquons.android.comunic.client.data.DatabaseContract.ConversationsMessagesSchema;
import java.util.ArrayList;
/**
* Conversation messages database helper
*
* @author Pierre HUBERT
* Created by pierre on 12/16/17.
*/
class ConversationMessagesDbHelper {
/**
* Debug tag
*/
private static final String TAG = "ConversationMessagesDbH";
/**
* Database helper object
*/
private DatabaseHelper dbHelper;
/**
* Conversations messages table name
*/
private static final String TABLE_NAME = ConversationsMessagesSchema.TABLE_NAME;
/**
* Conversation messages table column
*/
private final String[] columns = {
ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID,
ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID,
ConversationsMessagesSchema.COLUMN_NAME_USER_ID,
ConversationsMessagesSchema.COLUMN_NAME_IMAGE_PATH,
ConversationsMessagesSchema.COLUMN_NAME_MESSAGE,
ConversationsMessagesSchema.COLUMN_NAME_TIME_INSERT
};
/**
* Class constructor
*
* @param dbHelper Database helper
*/
ConversationMessagesDbHelper(@NonNull DatabaseHelper dbHelper){
this.dbHelper = dbHelper;
}
/**
* Get the last message stored of a conversation
*
* @param conversationID The ID of the target conversation
* @return The last message of the conversation or null if no message were found
*/
@Nullable
ConversationMessage getLast(int conversationID){
ConversationMessage message = null;
SQLiteDatabase db = dbHelper.getReadableDatabase();
//Prepare the query on the database
String selection = ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID + " = ?";
String[] selectionArgs = {""+conversationID};
String orderBy = ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID + " DESC";
String limit = "1";
//Perform the request
Cursor response = db.query(TABLE_NAME, columns, selection, selectionArgs, null,
null, orderBy, limit);
//Check for response
if(response.getCount() != 0){
response.moveToFirst();
message = getMessageFromCursorPos(response);
}
response.close();
db.close();
return message;
}
/**
* Insert a list of messages into the database
*
* @param list The list of messages to insert
* @return The result of the operation
*/
boolean insertMultiple(ArrayList<ConversationMessage> list){
SQLiteDatabase db = dbHelper.getWritableDatabase();
boolean success = true;
//Process the list of messages
for(ConversationMessage message : list){
//Try to insert the message
if(!insertOne(db, message))
success = false;
}
db.close();
return success;
}
/**
* Get an interval of messages from the database
*
* @param conv The conversation ID
* @param start The ID of the oldest message to fetch
* @param end The ID of the newest message to fetch
* @return The list of messages | null in case of failure
*/
ArrayList<ConversationMessage> getInterval(int conv, int start, int end){
SQLiteDatabase db = dbHelper.getReadableDatabase();
//Perform a request on the database
String selection = ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID + " = ? AND " +
" " + ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID + " >= ? AND " +
ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID + " <= ?";
String[] selectionArgs = {
""+conv,
""+start,
""+end
};
String order = ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID;
Cursor cur = db.query(TABLE_NAME, columns, selection, selectionArgs,
null, null, order);
//Process each response
ArrayList<ConversationMessage> list = new ArrayList<>();
cur.moveToFirst();
while(!cur.isAfterLast()){
list.add(getMessageFromCursorPos(cur));
cur.moveToNext();
}
//Close objects
cur.close();
db.close();
return list;
}
/**
* Insert a single message into the database
*
* @param db Database object (with writeable access)
* @param message The message to insert into the database
* @return TRUE in case of success / FALSE else
*/
private boolean insertOne(SQLiteDatabase db, ConversationMessage message){
//Perform the query
return db.insert(TABLE_NAME, null, getContentValues(message)) != -1;
}
/**
* Fill a message object based on a current cursor positin
*
* @param cursor The response cursor
* @return ConversationMessage object
*/
private ConversationMessage getMessageFromCursorPos(Cursor cursor){
ConversationMessage message = new ConversationMessage();
//Query database result
message.setId(cursor.getInt(cursor.getColumnIndexOrThrow(
ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID)));
message.setConversation_id(cursor.getInt(cursor.getColumnIndexOrThrow(
ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID)));
message.setUser_id(cursor.getInt(cursor.getColumnIndexOrThrow(
ConversationsMessagesSchema.COLUMN_NAME_USER_ID)));
message.setImage_path(cursor.getString(cursor.getColumnIndexOrThrow(
ConversationsMessagesSchema.COLUMN_NAME_IMAGE_PATH)));
message.setContent(cursor.getString(cursor.getColumnIndexOrThrow(
ConversationsMessagesSchema.COLUMN_NAME_MESSAGE)));
message.setTime_insert(cursor.getInt(cursor.getColumnIndexOrThrow(
ConversationsMessagesSchema.COLUMN_NAME_TIME_INSERT)));
return message;
}
/**
* Generate a ContentValue from a message object
*
* @param message The message to convert
* @return The generated ContentValues
*/
private ContentValues getContentValues(ConversationMessage message){
//Generate the ContentValues and return it
ContentValues cv = new ContentValues();
cv.put(ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID, message.getId());
cv.put(ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID,
message.getConversation_id());
cv.put(ConversationsMessagesSchema.COLUMN_NAME_USER_ID, message.getUser_id());
cv.put(ConversationsMessagesSchema.COLUMN_NAME_IMAGE_PATH, message.getImagePathNotNull());
cv.put(ConversationsMessagesSchema.COLUMN_NAME_MESSAGE, message.getContent());
cv.put(ConversationsMessagesSchema.COLUMN_NAME_TIME_INSERT, message.getTime_insert());
return cv;
}
}

View File

@ -0,0 +1,181 @@
package org.communiquons.android.comunic.client.data.conversations;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import org.communiquons.android.comunic.client.api.APIRequest;
import org.communiquons.android.comunic.client.api.APIRequestParameters;
import org.communiquons.android.comunic.client.api.APIResponse;
import org.communiquons.android.comunic.client.data.DatabaseHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* Conversation messages helper
*
* @author Pierre HUBERT
* Created by pierre on 12/16/17.
*/
public class ConversationMessagesHelper {
/**
* Debug tag
*/
private static final String TAG = "ConversationMessagesHel";
/**
* Conversations messages database helper
*/
private ConversationMessagesDbHelper mDbHelper;
/**
* Context of execution of the application
*/
private Context mContext;
/**
* Public constructor of the helper
*
* @param context The context of execution of the application
* @param dbHelper Database helper associated with the context
*/
public ConversationMessagesHelper(Context context, DatabaseHelper dbHelper){
mContext = context;
mDbHelper = new ConversationMessagesDbHelper(dbHelper);
}
/**
* Get the latest messages of a conversation
*
* @param conversation_id The ID of the conversation to refresh
* @return The ID of the last message available in the database
*/
int refresh_conversation(int conversation_id){
//Get the ID of the last message available in the database
int last_message_id = getLastIDFromDb(conversation_id);
//Perform a request on the database
ArrayList<ConversationMessage> new_messages = downloadNew(conversation_id, last_message_id);
//Check for errors
if(new_messages == null){
//An error occurred
return -1;
}
//Add the new messages to the database (if any)
if(new_messages.size() > 0) {
mDbHelper.insertMultiple(new_messages);
}
//Get the last message ID from database again
return getLastIDFromDb(conversation_id);
}
/**
* Fetch messages from the database
*
* @param conv Conversation ID
* @param start The ID of the oldest message to fetch
* @param end The ID of the last message to fetch
* @return The message of the interval, or null in case of failure
*/
@Nullable
ArrayList<ConversationMessage> getInDb(int conv, int start, int end){
return mDbHelper.getInterval(conv, start, end);
}
/**
* Get the ID of the last message of the conversation from the database
*
* @param conversation_id Target conversation
* @return The ID of the last message available in the database or 0 in case of failure
*/
private int getLastIDFromDb(int conversation_id){
//Get the id of the last message available in the database
ConversationMessage last_message = mDbHelper.getLast(conversation_id);
//Check if there isn't any message
if(last_message == null)
return 0; //There is no message in the database yet
//Return the ID of the last message available
else
return last_message.getId();
}
/**
* Download the latest messages available in the API
*
* @param conversationID The ID of the target conversation
* @param last_message_id The ID of the last known message (0 for none)
* @return null in case of failure, an empty array if there is no new messages available of the
* list of new messages for the specified conversation
*/
@Nullable
private ArrayList<ConversationMessage> downloadNew(int conversationID, int last_message_id){
//Prepare a request on the API
APIRequestParameters params = new APIRequestParameters(mContext,
"conversations/refresh_single");
params.addParameter("conversationID", ""+conversationID);
params.addParameter("last_message_id", ""+last_message_id);
ArrayList<ConversationMessage> list = new ArrayList<>();
try {
//Perform the request
APIResponse response = new APIRequest().exec(params);
//Get the list of new messages
JSONArray messages = response.getJSONArray();
for(int i = 0; i < messages.length(); i++){
//Convert the message into a message object
list.add(getMessageObject(conversationID, messages.getJSONObject(i)));
}
} catch (Exception e){
Log.e(TAG, "Couldn't refresh the list of messages!");
e.printStackTrace();
return null;
}
return list;
}
/**
* Convert a JSON object into a conversation message element
*
* @param convID The ID of the current conversation
* @param obj The target object
* @return Generation Conversation Message object
* @throws JSONException If the JSON objected couldn't be decoded
*/
private ConversationMessage getMessageObject(int convID, JSONObject obj) throws JSONException{
ConversationMessage message = new ConversationMessage();
//Get the message values
message.setId(obj.getInt("ID"));
message.setConversation_id(convID);
message.setUser_id(obj.getInt("ID_user"));
message.setImage_path(obj.getString("image_path"));
message.setContent(obj.getString("message"));
message.setTime_insert(obj.getInt("time_insert"));
return message;
}
}

View File

@ -0,0 +1,163 @@
package org.communiquons.android.comunic.client.data.conversations;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.ArrayList;
/**
* Runnable that refresh in the background the list of messages
*
* @author Pierre HUBERT
* Created by pierre on 12/16/17.
*/
public class ConversationRefreshRunnable implements Runnable {
/**
* Debug tag
*/
private String TAG = "ConversationRefreshRunn";
/**
* The ID of the conversation
*/
private int conversation_id;
/**
* The ID of the last message available
*/
private int last_message_id;
/**
* Conversation message helper
*/
private ConversationMessagesHelper convMessHelper;
/**
* Set to true to make the thread exit
*/
private boolean quit = false;
/**
* Object that helps to make breaks between refreshes
*/
private final Object object = new Object();
/**
* Create a new conversation refresh runnable
*
* @param conversation_id The ID of the conversation
* @param last_message_id The ID of the last message already present in the list (set to 0
* for no message)
* @param conversationMessagesHelper Conversation message helper to get access to the database
* and to be able to query the API through helper
*/
public ConversationRefreshRunnable(int conversation_id, int last_message_id,
@NonNull ConversationMessagesHelper conversationMessagesHelper){
this.conversation_id = conversation_id;
this.last_message_id = last_message_id;
this.convMessHelper = conversationMessagesHelper;
}
/**
* onMessagesChangeListener
*
* This interface is used to perform callback actions on the UI Thread to add messages
* to a list for example
*
* This method also changes in the conversations
*/
public interface onMessagesChangeListener {
/**
* Add new messages to a previous list of messages
*
* @param messages The new messagess
*/
void onAddMessage(@NonNull ArrayList<ConversationMessage> messages);
/**
* This method is called when there is not any message in the conversation
*
* Warning ! This method may be called several time
*/
void onNoMessage();
/**
* This method is called when an error occur on a request on the database and / or on the
* remote server
*/
void onError();
}
@Override
public void run() {
//Log action
Log.v(TAG, "Started conversation refresh runnable.");
synchronized (object) {
//Loop that execute indefinitely until the fragment is stopped
while (!quit) {
//Refresh the list of message from the server - the function return the ID of the last
// message available
int lastMessageInDb = convMessHelper.refresh_conversation(conversation_id);
//If the last message in the database is newer than the last message of the caller
if (lastMessageInDb > last_message_id) {
//Fetch all the messages available in the database since the last request
ArrayList<ConversationMessage> newMessages = convMessHelper.getInDb(
conversation_id,
last_message_id + 1,
lastMessageInDb
);
//Check for errors
if(newMessages == null){
//Callback : an error occurred.
Log.e(TAG, "Couldn't get the list of new messages from local database !");
}
else {
//Use the callback to send the messages to the UI thread
for(ConversationMessage message: newMessages)
Log.v(TAG, "Message: " + message.getContent());
//Update the ID of the last message fetched
last_message_id = lastMessageInDb;
}
}
if(lastMessageInDb == -1){
//Callback : an error occurred
Log.e(TAG, "Couldn't get the list of new messages !");
}
//Make a small break
try {
object.wait(500);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
Log.v(TAG, "Stopped conversation refresh runnable.");
}
/**
* Make the thread quit safely (does not interrupt currently running operation)
*/
public void quitSafely(){
quit = true;
}
}

View File

@ -9,6 +9,12 @@ import android.view.ViewGroup;
import android.widget.Toast;
import org.communiquons.android.comunic.client.R;
import org.communiquons.android.comunic.client.data.DatabaseHelper;
import org.communiquons.android.comunic.client.data.conversations.ConversationMessage;
import org.communiquons.android.comunic.client.data.conversations.ConversationMessagesHelper;
import org.communiquons.android.comunic.client.data.conversations.ConversationRefreshRunnable;
import java.util.ArrayList;
/**
* Conversation fragment
@ -36,11 +42,36 @@ public class ConversationFragment extends Fragment {
*/
private int conversation_id;
/**
* The last available message id
*/
private int last_message_id = 0;
/**
* The list of messages
*/
private ArrayList<ConversationMessage> messagesList;
/**
* Conversation refresh runnable
*/
private ConversationRefreshRunnable refreshRunnable;
/**
* Conversation messages helper
*/
private ConversationMessagesHelper convMessHelper;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Database helper
DatabaseHelper dbHelper = new DatabaseHelper(getActivity());
//Set conversation message helper
convMessHelper = new ConversationMessagesHelper(getActivity(), dbHelper);
//Get the conversation ID
conversation_id = getArguments().getInt(ARG_CONVERSATION_ID);
@ -52,14 +83,31 @@ public class ConversationFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_conversation, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
Toast.makeText(getActivity(), "Open : " + conversation_id, Toast.LENGTH_SHORT).show();
@Override
public void onResume() {
super.onResume();
refreshRunnable = new ConversationRefreshRunnable(conversation_id, last_message_id,
convMessHelper);
//Create and start the thread
new Thread(refreshRunnable).start();
}
@Override
public void onPause() {
super.onPause();
refreshRunnable.quitSafely();
}
}