در قسمت اول و قسمت دوم در مقاله ساخت اپلیکیشن چت با استفاده از فایربیس، ما برنامهی سرور شامل REST API و پنل مدیریت را ساختیم و همچنین نمونه سازی از gcm یکپارچه را با چند تست ابتدایی آموزش دادیم.
این قسمت سوم ساخت اپلیکیشن چت با فایربیس یک سناریوی کامل و پیشفرض از کاربرد GCM را با فراهم کردن اطلاعات مهمی – مانند انجام اعمال پویا و بروزرسانی UI بسته به نوع اعلان – نمایش میدهد. فعالیتهایی مثل افزایش شمارندهی پیام در لیست، نمایش خودکار پیام در صفحه نمایش چت توضیح خواهد شد. با قسمت سوم ساخت اپلیکیشن فایربیس و PHP با تیک4 همراه باشید.
ساخت برنامهی چت لحظهای
برنامهی چت در اصل شامل سه صفحه است. اول صفحهی لاگین است، جایی که کاربر نام و ایمیل خود را وارد میکند. دوم صفحهی لیست اتاق چت است، جایی که لیست اتاق چتها قرار دارد. صفحهی سوم بحث یک اتاق چت، جایی که پیامهای بحث به سمت چپ و راست، تراز میشوند.
افزودن پشتیبانی از volley
ما از کتابخانهی volley اندروید برای ساخت تمام درخواستهای endpoint های apiی rest استفاده میکنیم. Volley یک راه آسان برای مدیریت درخواستهای http و پاسخ فراهم میکند.
- فایل build.gradle که در زیر پوشهی app قرار دارد باز کنید و وابستگی volley را اضافه کنید و پروژه را دوباره build کنید.
build.gradle dependencies { ... compile 'com.mcxiaoke.volley:library-aar:1.0.0' }
- فایل MyApplication.java که در زیر پکیج app قرار دارد باز کنید و کد را مطابق کد زیر تغییر دهید. اینجا ما یک نمونهی singleton از RequestQueueی volley ایجاد میکنیم.
MyApplication.java package info.tik4.gcm.app; /** * Created by Lincoln on 14/10/15. */ import android.app.Application; import android.content.Intent; import android.text.TextUtils; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; import info.tik4.gcm.activity.LoginActivity; import info.tik4.gcm.helper.MyPreferenceManager; /** * Created by Ravi on 13/05/15. */ public class MyApplication extends Application { public static final String TAG = MyApplication.class .getSimpleName(); private RequestQueue mRequestQueue; private static MyApplication mInstance; private MyPreferenceManager pref; @Override public void onCreate() { super.onCreate(); mInstance = this; } public static synchronized MyApplication getInstance() { return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null) { mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } return mRequestQueue; } public MyPreferenceManager getPrefManager() { if (pref == null) { pref = new MyPreferenceManager(this); } return pref; } public <T> void addToRequestQueue(Request<T> req, String tag) { req.setTag(TextUtils.isEmpty(tag) ? TAG : tag); getRequestQueue().add(req); } public <T> void addToRequestQueue(Request<T> req) { req.setTag(TAG); getRequestQueue().add(req); } public void cancelPendingRequests(Object tag) { if (mRequestQueue != null) { mRequestQueue.cancelAll(tag); } } public void logout() { pref.clear(); Intent intent = new Intent(this, LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } }
3.یک پکیج به نام model ایجاد کنید. درون پکیج model سه کلاس به نامهای user.java، Message.java و ChatRoom.java را ایجاد کنید. این کلاسها برای ایجاد اشیاء در زمان پردازش پاسخ جیسون استفاده خواهند شد. ممکن است متوجه شده باشید که این کلاسها Serializable پیاده سازی کردهایم، که به ما اجازه میدهد اشیاء را با استفاده از intent ها به یک activity پاس بدهیم.
User.java package info.tik4.gcm.model; import java.io.Serializable; public class User implements Serializable { String id, name, email; public User() { } public User(String id, String name, String email) { this.id = id; this.name = name; this.email = email; } 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 getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
Message.java package info.tik4.gcm.model; import java.io.Serializable; public class Message implements Serializable { String id, message, createdAt; User user; public Message() { } public Message(String id, String message, String createdAt, User user) { this.id = id; this.message = message; this.createdAt = createdAt; this.user = user; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
ChatRoom.java package info.tik4.gcm.model; import java.io.Serializable; public class ChatRoom implements Serializable { String id, name, lastMessage, timestamp; int unreadCount; public ChatRoom() { } public ChatRoom(String id, String name, String lastMessage, String timestamp, int unreadCount) { this.id = id; this.name = name; this.lastMessage = lastMessage; this.timestamp = timestamp; this.unreadCount = unreadCount; } 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 getLastMessage() { return lastMessage; } public void setLastMessage(String lastMessage) { this.lastMessage = lastMessage; } public int getUnreadCount() { return unreadCount; } public void setUnreadCount(int unreadCount) { this.unreadCount = unreadCount; } public String getTimestamp() { return timestamp; } public void setTimestamp(String timestamp) { this.timestamp = timestamp; } }
- فایل MyPreferenceManager.java را باز کنید و متدهای storeUser() و getUser() را که اطلاعات کاربر را در preferences مشترک ذخیره میکند، اضافه کنید. این متدها وقتی که کاربر با موفقیت لاگین شد فراخوانی میشوند.
MyPreferenceManager.java package info.tik4.gcm.helper; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import info.tik4.gcm.model.User; /** * Created by Lincoln on 07/01/16. */ public class MyPreferenceManager { private String TAG = MyPreferenceManager.class.getSimpleName(); // Shared Preferences SharedPreferences pref; // Editor for Shared preferences SharedPreferences.Editor editor; // Context Context _context; // Shared pref mode int PRIVATE_MODE = 0; // Sharedpref file name private static final String PREF_NAME = "tik4_gcm"; // All Shared Preferences Keys private static final String KEY_USER_ID = "user_id"; private static final String KEY_USER_NAME = "user_name"; private static final String KEY_USER_EMAIL = "user_email"; private static final String KEY_NOTIFICATIONS = "notifications"; // Constructor public MyPreferenceManager(Context context) { this._context = context; pref = _context.getSharedPreferences(PREF_NAME, PRIVATE_MODE); editor = pref.edit(); } public void storeUser(User user) { editor.putString(KEY_USER_ID, user.getId()); editor.putString(KEY_USER_NAME, user.getName()); editor.putString(KEY_USER_EMAIL, user.getEmail()); editor.commit(); Log.e(TAG, "User is stored in shared preferences. " + user.getName() + ", " + user.getEmail()); } public User getUser() { if (pref.getString(KEY_USER_ID, null) != null) { String id, name, email; id = pref.getString(KEY_USER_ID, null); name = pref.getString(KEY_USER_NAME, null); email = pref.getString(KEY_USER_EMAIL, null); User user = new User(id, name, email); return user; } return null; } public void addNotification(String notification) { // get old notifications String oldNotifications = getNotifications(); if (oldNotifications != null) { oldNotifications += "|" + notification; } else { oldNotifications = notification; } editor.putString(KEY_NOTIFICATIONS, oldNotifications); editor.commit(); } public String getNotifications() { return pref.getString(KEY_NOTIFICATIONS, null); } public void clear() { editor.clear(); editor.commit(); } }
- EndPoints.java را درون پکیج app ایجاد کنید. اینجا ما یوآرالهای endpoint REST API اعلان میکنیم. اگر شما برنامه را به صورت محلی تست میکنید، از آدرس ip کامپیوتری که سرویس php برروی آن در حال اجرا است استفاده کنید. با ادامه مقاله ساخت اپلیکیشن فایربیس و PHP همراه ما باشید.
EndPoints.java package info.tik4.gcm.app; public class EndPoints { // localhost url - public static final String BASE_URL = "http://192.168.0.101/gcm_chat/v1"; public static final String LOGIN = BASE_URL + "/user/login"; public static final String USER = BASE_URL + "/user/_ID_"; public static final String CHAT_ROOMS = BASE_URL + "/chat_rooms"; public static final String CHAT_THREAD = BASE_URL + "/chat_rooms/_ID_"; public static final String CHAT_ROOM_MESSAGE = BASE_URL + "/chat_rooms/_ID_/message"; }
- GcmIntentService.java که در زیر بستهی gcm قرار دارد باز کنید و کد را به صورت زیر تغییر دهید. اینجا ما کد متد sendRegistrationToServer()تغییر داده ایم برای اینکه توکن ثبت نام gcm را به سرورمان ارسال کند تا پایگاه دادهی MySQL بروزرسانی شود.
package info.tik4.gcm.gcm; import android.app.IntentService; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.widget.Toast; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.google.android.gms.gcm.GcmPubSub; import com.google.android.gms.gcm.GoogleCloudMessaging; import com.google.android.gms.iid.InstanceID; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.HashMap; import java.util.Map; import info.tik4.gcm.R; import info.tik4.gcm.app.Config; import info.tik4.gcm.app.EndPoints; import info.tik4.gcm.app.MyApplication; import info.tik4.gcm.model.User; public class GcmIntentService extends IntentService { private static final String TAG = GcmIntentService.class.getSimpleName(); public GcmIntentService() { super(TAG); } public static final String KEY = "key"; public static final String TOPIC = "topic"; public static final String SUBSCRIBE = "subscribe"; public static final String UNSUBSCRIBE = "unsubscribe"; @Override protected void onHandleIntent(Intent intent) { String key = intent.getStringExtra(KEY); switch (key) { case SUBSCRIBE: // subscribe to a topic String topic = intent.getStringExtra(TOPIC); subscribeToTopic(topic); break; case UNSUBSCRIBE: break; default: // if key is specified, register with GCM registerGCM(); } } /** * Registering with GCM and obtaining the gcm registration id */ private void registerGCM() { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); try { InstanceID instanceID = InstanceID.getInstance(this); String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); Log.e(TAG, "GCM Registration Token: " + token); // sending the registration id to our server sendRegistrationToServer(token); sharedPreferences.edit().putBoolean(Config.SENT_TOKEN_TO_SERVER, true).apply(); } catch (Exception e) { Log.e(TAG, "Failed to complete token refresh", e); sharedPreferences.edit().putBoolean(Config.SENT_TOKEN_TO_SERVER, false).apply(); } // Notify UI that registration has completed, so the progress indicator can be hidden. Intent registrationComplete = new Intent(Config.REGISTRATION_COMPLETE); LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete); } private void sendRegistrationToServer(final String token) { // checking for valid login session User user = MyApplication.getInstance().getPrefManager().getUser(); if (user == null) { // TODO // user not found, redirecting him to login screen return; } String endPoint = EndPoints.USER.replace("_ID_", user.getId()); Log.e(TAG, "endpoint: " + endPoint); StringRequest strReq = new StringRequest(Request.Method.PUT, endPoint, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.e(TAG, "response: " + response); try { JSONObject obj = new JSONObject(response); // check for error if (obj.getBoolean("error") == false) { // broadcasting token sent to server Intent registrationComplete = new Intent(Config.SENT_TOKEN_TO_SERVER); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(registrationComplete); } else { Toast.makeText(getApplicationContext(), "Unable to send gcm registration id to our sever. " + obj.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show(); } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "Json parse error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { NetworkResponse networkResponse = error.networkResponse; Log.e(TAG, "Volley error: " + error.getMessage() + ", code: " + networkResponse); Toast.makeText(getApplicationContext(), "Volley error: " + error.getMessage(), Toast.LENGTH_SHORT).show(); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<String, String>(); params.put("gcm_registration_id", token); Log.e(TAG, "params: " + params.toString()); return params; } }; //Adding request to request queue MyApplication.getInstance().addToRequestQueue(strReq); } /** * Subscribe to a topic */ public static void subscribeToTopic(String topic) { GcmPubSub pubSub = GcmPubSub.getInstance(MyApplication.getInstance().getApplicationContext()); InstanceID instanceID = InstanceID.getInstance(MyApplication.getInstance().getApplicationContext()); String token = null; try { token = instanceID.getToken(MyApplication.getInstance().getApplicationContext().getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); if (token != null) { pubSub.subscribe(token, "/topics/" + topic, null); Log.e(TAG, "Subscribed to topic: " + topic); } else { Log.e(TAG, "error: gcm registration id is null"); } } catch (IOException e) { Log.e(TAG, "Topic subscribe error. Topic: " + topic + ", error: " + e.getMessage()); Toast.makeText(MyApplication.getInstance().getApplicationContext(), "Topic subscribe error. Topic: " + topic + ", error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } public void unsubscribeFromTopic(String topic) { GcmPubSub pubSub = GcmPubSub.getInstance(getApplicationContext()); InstanceID instanceID = InstanceID.getInstance(getApplicationContext()); String token = null; try { token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); if (token != null) { pubSub.unsubscribe(token, ""); Log.e(TAG, "Unsubscribed from topic: " + topic); } else { Log.e(TAG, "error: gcm registration id is null"); } } catch (IOException e) { Log.e(TAG, "Topic unsubscribe error. Topic: " + topic + ", error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "Topic subscribe error. Topic: " + topic + ", error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }
اضافه کردن صفحه لاگین
با تمام فایلهای مورد نیاز در این محل، ما صفحهی اول برنامه را که همان صفحهی لاگین است اضافه کنیم. صفحهی لاگین برای این که نام و ایمیل کاربر را بگیرد اضافه میشود. توجه داشته باشید که این روش درستی برای احراز هویت نیست چون ورود پسورد ضروری نیست. هر کاربری با وارد کردن هر نام تصادفی و ایمیلی میتواند به برنامهی ما لاگین بشود. ساخت اپلیکیشن فایربیس و PHP همراه ما باشید.
- فایل strings.xml که در زیر values قرار دارد باز کنید و مقدار string زیر را به آن اضافه کنید.
strings.xml <resources> <string name="app_name">Google Cloud Messaging</string> <string name="action_settings">Settings</string> <string name="title_activity_login">LoginActivity</string> <string name="hint_name">Full Name</string> <string name="hint_email">Email</string> <string name="err_msg_name">Enter full name</string> <string name="err_msg_email">Enter valid email address</string> <string name="title_activity_chat_room_discussion">ChatRoomDiscussionActivity</string> <string name="action_logout">Logout</string> </resources>
- فایل colors.xml را باز کنید و مقدار رنگ زیر را به آن اضافه کنید.
colors.xml <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#7B1FA2</color> <color name="colorPrimaryDark">#6A1B9A</color> <color name="colorAccent">#EA80FC</color> <color name="list_divider">#dedede</color> <color name="bg_bubble_self">#E1E1E1</color> </resources>
- یک activity به نام LoginActivity.java با کلیک راست بر activity ⇒ New ⇒ Activity ⇒ Blank Activity ایجاد کنید. این همچنین فایلهای layout activity_login.xml و activity_login.xml layout را برای login activity میسازد.
- فایل activity_login.xml را باز کنید و layout را به صورت زیر تغییر دهید. اینجا ما appbar و یک toolbar اضافه کردهایم.
activity_login.xml <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="info.tik4.gcm.activity.LoginActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_login" /> </android.support.design.widget.CoordinatorLayout>
- فایل activity_login.xml را باز کنید و یک فرم لاگین ساده با فیلد ورودی نام و ایمیل در آن قرار دهید.
activity_login.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="info.tik4.gcm.activity.LoginActivity" tools:showIn="@layout/activity_login"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <android.support.design.widget.TextInputLayout android:id="@+id/input_layout_name" android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/input_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_name" /> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:id="@+id/input_layout_email" android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/input_email" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_email" /> </android.support.design.widget.TextInputLayout> <Button android:id="@+id/btn_enter" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Enter" /> </LinearLayout> <ProgressBar android:id="@+id/progressBar" android:layout_width="30dp" android:layout_height="30dp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_gravity="center" android:layout_marginBottom="10dp" android:indeterminateTint="@color/colorAccent" android:visibility="gone" android:indeterminateTintMode="src_atop" /> </RelativeLayout>
- LoginActivity.java را باز کنید و کد زیر را در آن وارد کنید.
- قبل از تنظیم contentView ما نشست کاربر را در preferences مشترک بررسی خواهیم کرد. اگر کاربر در حال حاضر لاگین شده باشد. MainActivity لانچ خواهد شد.
- متد login() یک درخواست http با پاس کردن نام و ایمیل به عنوان پارامترهای پست به endpoint لاگین میسازد. یک کاربر جدید در سرور ایجاد خواهد شد و پاسخ جیسون ارائه خواهد شد.
- بعد از پردازش json، نشست(session) کاربر با ذخیرهی شی(object) کاربر در shared preferences ایجاد خواهد شد و MainActivity اجرا خواهد شد.
LoginActivity.java package info.tik4.gcm.activity; import android.content.Intent; import android.os.Bundle; import android.support.design.widget.TextInputLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import org.json.JSONException; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; import info.tik4.gcm.R; import info.tik4.gcm.app.EndPoints; import info.tik4.gcm.app.MyApplication; import info.tik4.gcm.model.User; public class LoginActivity extends AppCompatActivity { private String TAG = LoginActivity.class.getSimpleName(); private EditText inputName, inputEmail; private TextInputLayout inputLayoutName, inputLayoutEmail; private Button btnEnter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /** * Check for login session. It user is already logged in * redirect him to main activity * */ if (MyApplication.getInstance().getPrefManager().getUser() != null) { startActivity(new Intent(this, MainActivity.class)); finish(); } setContentView(R.layout.activity_login); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); inputLayoutName = (TextInputLayout) findViewById(R.id.input_layout_name); inputLayoutEmail = (TextInputLayout) findViewById(R.id.input_layout_email); inputName = (EditText) findViewById(R.id.input_name); inputEmail = (EditText) findViewById(R.id.input_email); btnEnter = (Button) findViewById(R.id.btn_enter); inputName.addTextChangedListener(new MyTextWatcher(inputName)); inputEmail.addTextChangedListener(new MyTextWatcher(inputEmail)); btnEnter.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { login(); } }); } /** * logging in user. Will make http post request with name, email * as parameters */ private void login() { if (!validateName()) { return; } if (!validateEmail()) { return; } final String name = inputName.getText().toString(); final String email = inputEmail.getText().toString(); StringRequest strReq = new StringRequest(Request.Method.POST, EndPoints.LOGIN, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.e(TAG, "response: " + response); try { JSONObject obj = new JSONObject(response); // check for error flag if (obj.getBoolean("error") == false) { // user successfully logged in JSONObject userObj = obj.getJSONObject("user"); User user = new User(userObj.getString("user_id"), userObj.getString("name"), userObj.getString("email")); // storing user in shared preferences MyApplication.getInstance().getPrefManager().storeUser(user); // start main activity startActivity(new Intent(getApplicationContext(), MainActivity.class)); finish(); } else { // login error - simply toast the message Toast.makeText(getApplicationContext(), "" + obj.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show(); } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "Json parse error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { NetworkResponse networkResponse = error.networkResponse; Log.e(TAG, "Volley error: " + error.getMessage() + ", code: " + networkResponse); Toast.makeText(getApplicationContext(), "Volley error: " + error.getMessage(), Toast.LENGTH_SHORT).show(); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<>(); params.put("name", name); params.put("email", email); Log.e(TAG, "params: " + params.toString()); return params; } }; //Adding request to request queue MyApplication.getInstance().addToRequestQueue(strReq); } private void requestFocus(View view) { if (view.requestFocus()) { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } } // Validating name private boolean validateName() { if (inputName.getText().toString().trim().isEmpty()) { inputLayoutName.setError(getString(R.string.err_msg_name)); requestFocus(inputName); return false; } else { inputLayoutName.setErrorEnabled(false); } return true; } // Validating email private boolean validateEmail() { String email = inputEmail.getText().toString().trim(); if (email.isEmpty() || !isValidEmail(email)) { inputLayoutEmail.setError(getString(R.string.err_msg_email)); requestFocus(inputEmail); return false; } else { inputLayoutEmail.setErrorEnabled(false); } return true; } private static boolean isValidEmail(String email) { return !TextUtils.isEmpty(email) && android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches(); } private class MyTextWatcher implements TextWatcher { private View view; private MyTextWatcher(View view) { this.view = view; } public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { } public void afterTextChanged(Editable editable) { switch (view.getId()) { case R.id.input_name: validateName(); break; case R.id.input_email: validateEmail(); break; } } } }
- AndroidManifest.xml را باز کنید و LoginActivity را به عنوان Launcher activity قرار دهید.
افزودن صفحهی لیست چتروم
اتاقهای چت در یک مدل لیست RecyclerView که ما استفاده کردیم نمایش داده خواهند شد. بنابراین پشتیبانی recycler view را به build.gradle اضافه کنید.
- فایل build.gradle را باز کنید و وابستگی RecyclerView را به آن اضافه کنید و پروژه را rebuild کنید.
compile ‘com.android.support:recyclerview-v7:23.1.1’
در زیر وابستگیهای نهایی build.gradle ما قرار دارند
build.gradle dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'com.mcxiaoke.volley:library-aar:1.0.0' compile 'com.android.support:recyclerview-v7:23.1.1' compile "com.google.android.gms:play-services:8.3.0" }
- layoutی به نام chat_rooms_list_row.xml ایجاد کنید. این layout اطلاعات یک اتاق چت تنها مثل نام، آخرین پیام، تعداد پیامهای خوانده نشده و timestamp را در recycler view رندر میکند
chat_rooms_list_row <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="16dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp"> <TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:textColor="#444444" android:textStyle="bold" android:textSize="16dp" /> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/name" android:textColor="#888888" android:layout_marginTop="5dp" android:text="Seems gcm will take some time"/> <TextView android:id="@+id/timestamp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="12:00 AM" android:textSize="10dp" android:layout_alignParentRight="true" android:layout_alignParentTop="true"/> <TextView android:id="@+id/count" android:layout_width="20dp" android:layout_height="20dp" android:gravity="center" android:textSize="10dp" android:textColor="@android:color/white" android:layout_below="@id/timestamp" android:layout_marginTop="5dp" android:layout_alignParentRight="true" android:text="5" android:background="@drawable/bg_circle"/> </RelativeLayout>
- یک پکیج جدید به نام adapter ایجاد کنید. زیر adapter یک کلاس به نام ChatRoomsAdapter.java ایجاد کنید. این یک recycler view adapter است که داده را برای list view اتاق چت آماده میکند
ChatRoomsAdapter.java package info.tik4.gcm.adapter; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import info.tik4.gcm.R; import info.tik4.gcm.model.ChatRoom; public class ChatRoomsAdapter extends RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder> { private Context mContext; private ArrayList<ChatRoom> chatRoomArrayList; private static String today; public class ViewHolder extends RecyclerView.ViewHolder { public TextView name, message, timestamp, count; public ViewHolder(View view) { super(view); name = (TextView) view.findViewById(R.id.name); message = (TextView) view.findViewById(R.id.message); timestamp = (TextView) view.findViewById(R.id.timestamp); count = (TextView) view.findViewById(R.id.count); } } public ChatRoomsAdapter(Context mContext, ArrayList<ChatRoom> chatRoomArrayList) { this.mContext = mContext; this.chatRoomArrayList = chatRoomArrayList; Calendar calendar = Calendar.getInstance(); today = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.chat_rooms_list_row, parent, false); return new ViewHolder(itemView); } @Override public void onBindViewHolder(ViewHolder holder, int position) { ChatRoom chatRoom = chatRoomArrayList.get(position); holder.name.setText(chatRoom.getName()); holder.message.setText(chatRoom.getLastMessage()); if (chatRoom.getUnreadCount() > 0) { holder.count.setText(String.valueOf(chatRoom.getUnreadCount())); holder.count.setVisibility(View.VISIBLE); } else { holder.count.setVisibility(View.GONE); } holder.timestamp.setText(getTimeStamp(chatRoom.getTimestamp())); } @Override public int getItemCount() { return chatRoomArrayList.size(); } public static String getTimeStamp(String dateStr) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String timestamp = ""; today = today.length() < 2 ? "0" + today : today; try { Date date = format.parse(dateStr); SimpleDateFormat todayFormat = new SimpleDateFormat("dd"); String dateToday = todayFormat.format(date); format = dateToday.equals(today) ? new SimpleDateFormat("hh:mm a") : new SimpleDateFormat("dd LLL, hh:mm a"); String date1 = format.format(date); timestamp = date1.toString(); } catch (ParseException e) { e.printStackTrace(); } return timestamp; } public interface ClickListener { void onClick(View view, int position); void onLongClick(View view, int position); } public static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener { private GestureDetector gestureDetector; private ChatRoomsAdapter.ClickListener clickListener; public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ChatRoomsAdapter.ClickListener clickListener) { this.clickListener = clickListener; gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public void onLongPress(MotionEvent e) { View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null && clickListener != null) { clickListener.onLongClick(child, recyclerView.getChildPosition(child)); } } }); } @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { View child = rv.findChildViewUnder(e.getX(), e.getY()); if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) { clickListener.onClick(child, rv.getChildPosition(child)); } return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } } }
- فایل MyGcmPushReceiver.java را باز کنید و کد را به صورت زیر تغییر دهید. اینجا ما چند متد برای مدیریت پوش نوتیفیکیشنها اضافه کردهایم. اول پوش نوتیفیکیشن با flag نود مشخص میشود و بعد فعالیت درست انجام خواهد شد.
- متد processChatRoomPush() هر گاه که پیام پوش اتاق چت دریافت شود فراخوانی خواهد شد
- متد processUserMessage() هرگاه که پوش مختص کاربر لاگین شده است فراخوانی خواهد شد
MyGcmPushReceiver.java package info.tik4.gcm.gcm; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.google.android.gms.gcm.GcmListenerService; import org.json.JSONException; import org.json.JSONObject; import info.tik4.gcm.activity.ChatRoomActivity; import info.tik4.gcm.activity.MainActivity; import info.tik4.gcm.app.Config; import info.tik4.gcm.app.MyApplication; import info.tik4.gcm.model.Message; import info.tik4.gcm.model.User; public class MyGcmPushReceiver extends GcmListenerService { private static final String TAG = MyGcmPushReceiver.class.getSimpleName(); private NotificationUtils notificationUtils; /** * Called when message is received. * * @param from SenderID of the sender. * @param bundle Data bundle containing message data as key/value pairs. * For Set of keys use data.keySet(). */ @Override public void onMessageReceived(String from, Bundle bundle) { String title = bundle.getString("title"); Boolean isBackground = Boolean.valueOf(bundle.getString("is_background")); String flag = bundle.getString("flag"); String data = bundle.getString("data"); Log.d(TAG, "From: " + from); Log.d(TAG, "title: " + title); Log.d(TAG, "isBackground: " + isBackground); Log.d(TAG, "flag: " + flag); Log.d(TAG, "data: " + data); if (flag == null) return; if(MyApplication.getInstance().getPrefManager().getUser() == null){ // user is not logged in, skipping push notification Log.e(TAG, "user is not logged in, skipping push notification"); return; } if (from.startsWith("/topics/")) { // message received from some topic. } else { // normal downstream message. } switch (Integer.parseInt(flag)) { case Config.PUSH_TYPE_CHATROOM: // push notification belongs to a chat room processChatRoomPush(title, isBackground, data); break; case Config.PUSH_TYPE_USER: // push notification is specific to user processUserMessage(title, isBackground, data); break; } } /** * Processing chat room push message * this message will be broadcasts to all the activities registered * */ private void processChatRoomPush(String title, boolean isBackground, String data) { if (!isBackground) { try { JSONObject datObj = new JSONObject(data); String chatRoomId = datObj.getString("chat_room_id"); JSONObject mObj = datObj.getJSONObject("message"); Message message = new Message(); message.setMessage(mObj.getString("message")); message.setId(mObj.getString("message_id")); message.setCreatedAt(mObj.getString("created_at")); JSONObject uObj = datObj.getJSONObject("user"); // skip the message if the message belongs to same user as // the user would be having the same message when he was sending // but it might differs in your scenario if (uObj.getString("user_id").equals(MyApplication.getInstance().getPrefManager().getUser().getId())) { Log.e(TAG, "Skipping the push message as it belongs to same user"); return; } User user = new User(); user.setId(uObj.getString("user_id")); user.setEmail(uObj.getString("email")); user.setName(uObj.getString("name")); message.setUser(user); // verifying whether the app is in background or foreground if (!NotificationUtils.isAppIsInBackground(getApplicationContext())) { // app is in foreground, broadcast the push message Intent pushNotification = new Intent(Config.PUSH_NOTIFICATION); pushNotification.putExtra("type", Config.PUSH_TYPE_CHATROOM); pushNotification.putExtra("message", message); pushNotification.putExtra("chat_room_id", chatRoomId); LocalBroadcastManager.getInstance(this).sendBroadcast(pushNotification); // play notification sound NotificationUtils notificationUtils = new NotificationUtils(); notificationUtils.playNotificationSound(); } else { // app is in background. show the message in notification try Intent resultIntent = new Intent(getApplicationContext(), ChatRoomActivity.class); resultIntent.putExtra("chat_room_id", chatRoomId); showNotificationMessage(getApplicationContext(), title, user.getName() + " : " + message.getMessage(), message.getCreatedAt(), resultIntent); } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "Json parse error: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } else { // the push notification is silent, may be other operations needed // like inserting it in to SQLite } } /** * Processing user specific push message * It will be displayed with / without image in push notification tray * */ private void processUserMessage(String title, boolean isBackground, String data) { if (!isBackground) { try { JSONObject datObj = new JSONObject(data); String imageUrl = datObj.getString("image"); JSONObject mObj = datObj.getJSONObject("message"); Message message = new Message(); message.setMessage(mObj.getString("message")); message.setId(mObj.getString("message_id")); message.setCreatedAt(mObj.getString("created_at")); JSONObject uObj = datObj.getJSONObject("user"); User user = new User(); user.setId(uObj.getString("user_id")); user.setEmail(uObj.getString("email")); user.setName(uObj.getString("name")); message.setUser(user); // verifying whether the app is in background or foreground if (!NotificationUtils.isAppIsInBackground(getApplicationContext())) { // app is in foreground, broadcast the push message Intent pushNotification = new Intent(Config.PUSH_NOTIFICATION); pushNotification.putExtra("type", Config.PUSH_TYPE_USER); pushNotification.putExtra("message", message); LocalBroadcastManager.getInstance(this).sendBroadcast(pushNotification); // play notification sound NotificationUtils notificationUtils = new NotificationUtils(); notificationUtils.playNotificationSound(); } else { // app is in background. show the message in notification try Intent resultIntent = new Intent(getApplicationContext(), MainActivity.class); // check for push notification image attachment if (TextUtils.isEmpty(imageUrl)) { showNotificationMessage(getApplicationContext(), title, user.getName() + " : " + message.getMessage(), message.getCreatedAt(), resultIntent); } else { // push notification contains image // show it with the image showNotificationMessageWithBigImage(getApplicationContext(), title, message.getMessage(), message.getCreatedAt(), resultIntent, imageUrl); } } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "Json parse error: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } else { // the push notification is silent, may be other operations needed // like inserting it in to SQLite } } /** * Showing notification with text only * */ private void showNotificationMessage(Context context, String title, String message, String timeStamp, Intent intent) { notificationUtils = new NotificationUtils(context); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); notificationUtils.showNotificationMessage(title, message, timeStamp, intent); } /** * Showing notification with text and image * */ private void showNotificationMessageWithBigImage(Context context, String title, String message, String timeStamp, Intent intent, String imageUrl) { notificationUtils = new NotificationUtils(context); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); notificationUtils.showNotificationMessage(title, message, timeStamp, intent, imageUrl); } }
18.یک لایوته xml به نام line_divider.xml درون پوشهی drawable بسازید
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <size android:width="1dp" android:height="1dp" /> <solid android:color="@color/list_divider" /> </shape>
- یک فایل xml به نام bg_circle.xml درون پوشهی drawable بسازید. این لایوت به عنوان پس زمینهی گرد برای شماره پیامهای خوانده نشده عمل میکند.
bg_circle.xml <shape xmlns:android="http://schemas.android.com/apk/res/android" > <solid android:color="@color/colorPrimary" /> <corners android:bottomLeftRadius="20dp" android:bottomRightRadius="20dp" android:topLeftRadius="20dp" android:topRightRadius="20dp" /> </shape>
- زیر پکیج helper، SimpleDividerItemDecoration.java را ایجاد کنید. این کلاس به اضافه شدن جداکننده به آیتمهای recycler view کمک میکند.
SimpleDividerItemDecoration.java package info.tik4.gcm.helper; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.view.View; import info.tik4.gcm.R; /** * Created by Lincoln on 30/10/15. */ public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration { private Drawable mDivider; public SimpleDividerItemDecoration(Context context) { mDivider = ContextCompat.getDrawable(context, R.drawable.line_divider); } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getBottom() + params.bottomMargin; int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } }
- فایل menu_main.xml که در زیر res menu قرار دارد را باز کنید و یک آیتم منوی logout برای فراهم کردن امکان logout به کاربر اضافه کنید.
menu_main.xml <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".activity.MainActivity"> <item android:id="@+id/action_logout" android:orderInCategory="100" android:title="@string/action_logout" app:showAsAction="never" /> </menu>
- فایل content_main.xml را باز کنید و المان recycler view را اضافه کنید .
content_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:showIn="@layout/activity_main" tools:context=".activity.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="vertical" /> </RelativeLayout>
- در نهایت MainActivity.java را باز کنید و کد را به صورت زیر تغییر دهید.
- اول نشست کاربر پیش از تنظیم محتوای view بررسی میشود .
- در دریافت توکن ثبت نام gcm، کاربر به موضوع global مشترک میشود. این به ما اجازه میدهد که یک نوتیفیکیشن به تمام کاربران از پنل مدیریت ارسال کنیم.
- fetchChatRooms() در متد onCreate() فراخوانی میشود که اطلاعات تمام اتاقهای چت را از سرور واکشی میکند. زمانی که اتاق چتها دریافت شدند، کاربر به طور خودکار به تمام موضوعات اتاقهای چت مشترک میشود. بنابراین او شروع به دریافت نوتیفیکیشن، هر جا که یک بحث فعال در یک اتاق چت در جریان باشد، میکند.
- دریافت کنندهی برودکست ثبت نام نشده / ثبت نام شده در متدهای onResume() / onPause()
- دریافت کنندهی برودکست هر جا که یک پیام نوتیفیکیشن در جایی که متد handlePushNotification() صدا زده شود دریافت بشود، شلیک خواهد شد
- handlePushNotification() پوش نوتیفیکیشن با بروزرسانی آیتمهای recycler view را با بروزرسانی last message و unread message count مدیریت میکند. با ادامه مقاله ساخت اپلیکیشن فایربیس و PHP همراه ما باشید.
MainActivity.java package info.tik4.gcm.activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import info.tik4.gcm.R; import info.tik4.gcm.adapter.ChatRoomsAdapter; import info.tik4.gcm.app.Config; import info.tik4.gcm.app.EndPoints; import info.tik4.gcm.app.MyApplication; import info.tik4.gcm.gcm.GcmIntentService; import info.tik4.gcm.gcm.NotificationUtils; import info.tik4.gcm.helper.SimpleDividerItemDecoration; import info.tik4.gcm.model.ChatRoom; import info.tik4.gcm.model.Message; public class MainActivity extends AppCompatActivity { private String TAG = MainActivity.class.getSimpleName(); private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; private BroadcastReceiver mRegistrationBroadcastReceiver; private ArrayList<ChatRoom> chatRoomArrayList; private ChatRoomsAdapter mAdapter; private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /** * Check for login session. If not logged in launch * login activity * */ if (MyApplication.getInstance().getPrefManager().getUser() == null) { launchLoginActivity(); } setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); /** * Broadcast receiver calls in two scenarios * 1. gcm registration is completed * 2. when new push notification is received * */ mRegistrationBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // checking for type intent filter if (intent.getAction().equals(Config.REGISTRATION_COMPLETE)) { // gcm successfully registered // now subscribe to `global` topic to receive app wide notifications subscribeToGlobalTopic(); } else if (intent.getAction().equals(Config.SENT_TOKEN_TO_SERVER)) { // gcm registration id is stored in our server's MySQL Log.e(TAG, "GCM registration id is sent to our server"); } else if (intent.getAction().equals(Config.PUSH_NOTIFICATION)) { // new push notification is received handlePushNotification(intent); } } }; chatRoomArrayList = new ArrayList<>(); mAdapter = new ChatRoomsAdapter(this, chatRoomArrayList); LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); recyclerView.addItemDecoration(new SimpleDividerItemDecoration( getApplicationContext() )); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(mAdapter); recyclerView.addOnItemTouchListener(new ChatRoomsAdapter.RecyclerTouchListener(getApplicationContext(), recyclerView, new ChatRoomsAdapter.ClickListener() { @Override public void onClick(View view, int position) { // when chat is clicked, launch full chat thread activity ChatRoom chatRoom = chatRoomArrayList.get(position); Intent intent = new Intent(MainActivity.this, ChatRoomActivity.class); intent.putExtra("chat_room_id", chatRoom.getId()); intent.putExtra("name", chatRoom.getName()); startActivity(intent); } @Override public void onLongClick(View view, int position) { } })); /** * Always check for google play services availability before * proceeding further with GCM * */ if (checkPlayServices()) { registerGCM(); fetchChatRooms(); } } /** * Handles new push notification */ private void handlePushNotification(Intent intent) { int type = intent.getIntExtra("type", -1); // if the push is of chat room message // simply update the UI unread messages count if (type == Config.PUSH_TYPE_CHATROOM) { Message message = (Message) intent.getSerializableExtra("message"); String chatRoomId = intent.getStringExtra("chat_room_id"); if (message != null && chatRoomId != null) { updateRow(chatRoomId, message); } } else if (type == Config.PUSH_TYPE_USER) { // push belongs to user alone // just showing the message in a toast Message message = (Message) intent.getSerializableExtra("message"); Toast.makeText(getApplicationContext(), "New push: " + message.getMessage(), Toast.LENGTH_LONG).show(); } } /** * Updates the chat list unread count and the last message */ private void updateRow(String chatRoomId, Message message) { for (ChatRoom cr : chatRoomArrayList) { if (cr.getId().equals(chatRoomId)) { int index = chatRoomArrayList.indexOf(cr); cr.setLastMessage(message.getMessage()); cr.setUnreadCount(cr.getUnreadCount() + 1); chatRoomArrayList.remove(index); chatRoomArrayList.add(index, cr); break; } } mAdapter.notifyDataSetChanged(); } /** * fetching the chat rooms by making http call */ private void fetchChatRooms() { StringRequest strReq = new StringRequest(Request.Method.GET, EndPoints.CHAT_ROOMS, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.e(TAG, "response: " + response); try { JSONObject obj = new JSONObject(response); // check for error flag if (obj.getBoolean("error") == false) { JSONArray chatRoomsArray = obj.getJSONArray("chat_rooms"); for (int i = 0; i < chatRoomsArray.length(); i++) { JSONObject chatRoomsObj = (JSONObject) chatRoomsArray.get(i); ChatRoom cr = new ChatRoom(); cr.setId(chatRoomsObj.getString("chat_room_id")); cr.setName(chatRoomsObj.getString("name")); cr.setLastMessage(""); cr.setUnreadCount(0); cr.setTimestamp(chatRoomsObj.getString("created_at")); chatRoomArrayList.add(cr); } } else { // error in fetching chat rooms Toast.makeText(getApplicationContext(), "" + obj.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show(); } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "Json parse error: " + e.getMessage(), Toast.LENGTH_LONG).show(); } mAdapter.notifyDataSetChanged(); // subscribing to all chat room topics subscribeToAllTopics(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { NetworkResponse networkResponse = error.networkResponse; Log.e(TAG, "Volley error: " + error.getMessage() + ", code: " + networkResponse); Toast.makeText(getApplicationContext(), "Volley error: " + error.getMessage(), Toast.LENGTH_SHORT).show(); } }); //Adding request to request queue MyApplication.getInstance().addToRequestQueue(strReq); } // subscribing to global topic private void subscribeToGlobalTopic() { Intent intent = new Intent(this, GcmIntentService.class); intent.putExtra(GcmIntentService.KEY, GcmIntentService.SUBSCRIBE); intent.putExtra(GcmIntentService.TOPIC, Config.TOPIC_GLOBAL); startService(intent); } // Subscribing to all chat room topics // each topic name starts with `topic_` followed by the ID of the chat room // Ex: topic_1, topic_2 private void subscribeToAllTopics() { for (ChatRoom cr : chatRoomArrayList) { Intent intent = new Intent(this, GcmIntentService.class); intent.putExtra(GcmIntentService.KEY, GcmIntentService.SUBSCRIBE); intent.putExtra(GcmIntentService.TOPIC, "topic_" + cr.getId()); startService(intent); } } private void launchLoginActivity() { Intent intent = new Intent(MainActivity.this, LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); finish(); } @Override protected void onResume() { super.onResume(); // register GCM registration complete receiver LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver, new IntentFilter(Config.REGISTRATION_COMPLETE)); // register new push message receiver // by doing this, the activity will be notified each time a new message arrives LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver, new IntentFilter(Config.PUSH_NOTIFICATION)); // clearing the notification tray NotificationUtils.clearNotifications(); } @Override protected void onPause() { LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver); super.onPause(); } // starting the service to register with GCM private void registerGCM() { Intent intent = new Intent(this, GcmIntentService.class); intent.putExtra("key", "register"); startService(intent); } private boolean checkPlayServices() { GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); int resultCode = apiAvailability.isGooglePlayServicesAvailable(this); if (resultCode != ConnectionResult.SUCCESS) { if (apiAvailability.isUserResolvableError(resultCode)) { apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST) .show(); } else { Log.i(TAG, "This device is not supported. Google Play Services not installed!"); Toast.makeText(getApplicationContext(), "This device is not supported. Google Play Services not installed!", Toast.LENGTH_LONG).show(); finish(); } return false; } return true; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_main, menu); return true; } public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.action_logout: MyApplication.getInstance().logout(); break; } return super.onOptionsItemSelected(menuItem); } }
حالا برنامه را در هر دوی دستگاه/شبیه ساز برای تست پوش نوتیفیکیشن نصب کنید. شما همچنین میتوانید از برنامهی پنل مدیریتی سرور با مراجعه به آدرس http://localhost/gcm_chat استفاده کنید و یک پیام در اتاق چت تایپ کنید.
اضافه کردن تک ریسمان اتاق چت
صفحهی تک ریسمان اتاق چت شامل پیامهایی از کاربران مختلف است که به چپ و راست در صفحه تراز شده اند. تمام پیامهایی به کاربران دیگر تعلق دارند به سمت چپ تراز شده اند. پیامهای متعلق به کاربر جاری لاگ این شده به سمت راست تراز خواهد شد.
این ترازبندی میتواند با استفاده از یک recycler view و دو لایهی مختلف xml برای خود و دیگرپیامها انجام شود. با ادامه مقاله ساخت اپلیکیشن چت با فایربیس همراه ما باشید.
- یک فایل xml به نام bg_bubble_white.xml درون پوشهی drawable ایجاد کنید. این layout یک پسزمینه با گوشهی گرد به پیامهای دیگران اضافه میکند.
bg_bubble_white.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <!-- view background color --> <solid android:color="@android:color/white" > </solid> <!-- If you want to add some padding --> <padding android:left="10dp" android:top="4dp" android:right="10dp" android:bottom="4dp" > </padding> <!-- Here is the corner radius --> <corners android:radius="5dp" > </corners> </shape>
- یک فایل xml به نام bg_bubble_gray.xml درون پوشهی drawable ایجاد کنید. این layout یک پس زمینه با گوشهی گرد به پیامهای خودی اضافه میکند.
bg_bubble_gray.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <!-- view background color --> <solid android:color="@color/bg_bubble_self" > </solid> <!-- If you want to add some padding --> <padding android:left="10dp" android:top="4dp" android:right="10dp" android:bottom="4dp" > </padding> <!-- Here is the corner radius --> <corners android:radius="5dp" > </corners> </shape>
- دو لایوت xml به نامهای chat_item_self.xml و chat_item_other.xml برای رندر کردن هر دو پیامهای خودی و پیامهای دیگران ایجاد کنید.
chat_item_self.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="right" android:orientation="horizontal" android:paddingBottom="5dp" android:paddingLeft="16dp" android:paddingRight="16dp"> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:textIsSelectable="true" android:background="@drawable/bg_bubble_gray" android:textSize="14dp" /> <TextView android:id="@+id/timestamp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignRight="@id/message" android:layout_below="@id/message" android:layout_marginBottom="25dp" android:padding="10dp" android:textSize="10dp" /> </RelativeLayout>
chat_item_other.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:paddingTop="5dp" android:paddingLeft="16dp" android:paddingRight="16dp"> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginLeft="10dp" android:textIsSelectable="true" android:background="@drawable/bg_bubble_white" android:textSize="14dp" /> <TextView android:id="@+id/timestamp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@id/message" android:layout_marginBottom="25dp" android:layout_marginLeft="10dp" android:paddingLeft="10dp" android:paddingTop="6dp" android:textSize="10dp" /> </RelativeLayout>
- یک کلاس به نام ChatRoomThreadAdapter.java زیر پکیج adapter ایجاد کنید.
ChatRoomThreadAdapter.java This adapter class identifies the current logged in user messages by user id and align the messages left or right by inflating two different xml layouts. package info.tik4.gcm.adapter; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import info.tik4.gcm.R; import info.tik4.gcm.model.Message; public class ChatRoomThreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static String TAG = ChatRoomThreadAdapter.class.getSimpleName(); private String userId; private int SELF = 100; private static String today; private Context mContext; private ArrayList<Message> messageArrayList; public class ViewHolder extends RecyclerView.ViewHolder { TextView message, timestamp; public ViewHolder(View view) { super(view); message = (TextView) itemView.findViewById(R.id.message); timestamp = (TextView) itemView.findViewById(R.id.timestamp); } } public ChatRoomThreadAdapter(Context mContext, ArrayList<Message> messageArrayList, String userId) { this.mContext = mContext; this.messageArrayList = messageArrayList; this.userId = userId; Calendar calendar = Calendar.getInstance(); today = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView; // view type is to identify where to render the chat message // left or right if (viewType == SELF) { // self message itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.chat_item_self, parent, false); } else { // others message itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.chat_item_other, parent, false); } return new ViewHolder(itemView); } @Override public int getItemViewType(int position) { Message message = messageArrayList.get(position); if (message.getUser().getId().equals(userId)) { return SELF; } return position; } @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { Message message = messageArrayList.get(position); ((ViewHolder) holder).message.setText(message.getMessage()); String timestamp = getTimeStamp(message.getCreatedAt()); if (message.getUser().getName() != null) timestamp = message.getUser().getName() + ", " + timestamp; ((ViewHolder) holder).timestamp.setText(timestamp); } @Override public int getItemCount() { return messageArrayList.size(); } public static String getTimeStamp(String dateStr) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String timestamp = ""; today = today.length() < 2 ? "0" + today : today; try { Date date = format.parse(dateStr); SimpleDateFormat todayFormat = new SimpleDateFormat("dd"); String dateToday = todayFormat.format(date); format = dateToday.equals(today) ? new SimpleDateFormat("hh:mm a") : new SimpleDateFormat("dd LLL, hh:mm a"); String date1 = format.format(date); timestamp = date1.toString(); } catch (ParseException e) { e.printStackTrace(); } return timestamp; } }
- یک کلاس اکتیویتی جدید به نام ChatRoomActivity.java با راست کلیک بر activity ⇒ New ⇒ Activity ⇒ Blank Activity ایجاد کنید. این دو فایل لایوت با نامهای content_chat_room.xmlو activity_chat_room.xml ایجاد میکند.
activity_chat_room.xml <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".activity.ChatRoomActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_chat_room" /> </android.support.design.widget.CoordinatorLayout>
- content_chat_room.xml را باز کنید و کد زیر را به آن اضافه کنید. اینجا ما recycler view و یک آیتم EditText را برای وارد کردن پیام جدید در اتاق چت اضافه کردهایم.
content_chat_room.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".activity.ChatRoomActivity" tools:showIn="@layout/activity_chat_room"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="20dp" android:scrollbars="vertical" /> <LinearLayout android:background="@android:color/white" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="horizontal" android:weightSum="4"> <EditText android:id="@+id/message" android:layout_width="0dp" android:hint="Enter message" android:paddingLeft="10dp" android:background="@null" android:layout_marginRight="10dp" android:layout_marginLeft="16dp" android:lines="1" android:layout_height="wrap_content" android:layout_weight="3" /> <Button android:id="@+id/btn_send" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@null" android:text="SEND" android:textSize="16dp" android:textColor="@color/colorPrimary" /> </LinearLayout> </RelativeLayout>
- ChatRoomActivity.java را باز کنید و کد را به صورت زیر تغییر دهید.
- متد fetchChatThread() تمام پیامهای ردوبدل شدهی قبلی را واکشی میکند به اتاق چت.
- دریافت کنندهی برودکست برای PUSH_NOTIFICATION در متد onResume() ثبت شده است
- متد handlePushNotification() پیام پوش را مدیریت میکند و به لیست آرایهی adapter ضمیمه میکند. به محض فراخوانی notifyDataSetChanged() ، پیام جدید در ریسمان مباحثه نمایش داده میشود.
- پیامهای خودی و دیگران با مقایسهی user id در پیام پوش با idی کاربری که در حال حاضر لاگ این است، به سمت چپ یا راست تراز میشوند
- متد sendMessage() یک پیام جدید برای پست در اتاق چت، به سرور میفرستد. در سرور پیام برای برودکست به دستگاههای دیگری که به موضوع اتاق چت مشترک شده اند، به سرور gcm ارسال میشود. با ادامه مقاله ساخت اپلیکیشن چت با فایربیس باما همراه باشید.
ChatRoomActivity.java package info.tik4.gcm.activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.RetryPolicy; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import info.tik4.gcm.R; import info.tik4.gcm.adapter.ChatRoomThreadAdapter; import info.tik4.gcm.app.Config; import info.tik4.gcm.app.EndPoints; import info.tik4.gcm.app.MyApplication; import info.tik4.gcm.gcm.NotificationUtils; import info.tik4.gcm.model.Message; import info.tik4.gcm.model.User; public class ChatRoomActivity extends AppCompatActivity { private String TAG = ChatRoomActivity.class.getSimpleName(); private String chatRoomId; private RecyclerView recyclerView; private ChatRoomThreadAdapter mAdapter; private ArrayList<Message> messageArrayList; private BroadcastReceiver mRegistrationBroadcastReceiver; private EditText inputMessage; private Button btnSend; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat_room); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); inputMessage = (EditText) findViewById(R.id.message); btnSend = (Button) findViewById(R.id.btn_send); Intent intent = getIntent(); chatRoomId = intent.getStringExtra("chat_room_id"); String title = intent.getStringExtra("name"); getSupportActionBar().setTitle(title); getSupportActionBar().setDisplayHomeAsUpEnabled(true); if (chatRoomId == null) { Toast.makeText(getApplicationContext(), "Chat room not found!", Toast.LENGTH_SHORT).show(); finish(); } recyclerView = (RecyclerView) findViewById(R.id.recycler_view); messageArrayList = new ArrayList<>(); // self user id is to identify the message owner String selfUserId = MyApplication.getInstance().getPrefManager().getUser().getId(); mAdapter = new ChatRoomThreadAdapter(this, messageArrayList, selfUserId); LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(mAdapter); mRegistrationBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Config.PUSH_NOTIFICATION)) { // new push message is received handlePushNotification(intent); } } }; btnSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendMessage(); } }); fetchChatThread(); } @Override protected void onResume() { super.onResume(); // registering the receiver for new notification LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver, new IntentFilter(Config.PUSH_NOTIFICATION)); NotificationUtils.clearNotifications(); } @Override protected void onPause() { LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver); super.onPause(); } /** * Handling new push message, will add the message to * recycler view and scroll it to bottom * */ private void handlePushNotification(Intent intent) { Message message = (Message) intent.getSerializableExtra("message"); String chatRoomId = intent.getStringExtra("chat_room_id"); if (message != null && chatRoomId != null) { messageArrayList.add(message); mAdapter.notifyDataSetChanged(); if (mAdapter.getItemCount() > 1) { recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, mAdapter.getItemCount() - 1); } } } /** * Posting a new message in chat room * will make an http call to our server. Our server again sends the message * to all the devices as push notification * */ private void sendMessage() { final String message = this.inputMessage.getText().toString().trim(); if (TextUtils.isEmpty(message)) { Toast.makeText(getApplicationContext(), "Enter a message", Toast.LENGTH_SHORT).show(); return; } String endPoint = EndPoints.CHAT_ROOM_MESSAGE.replace("_ID_", chatRoomId); Log.e(TAG, "endpoint: " + endPoint); this.inputMessage.setText(""); StringRequest strReq = new StringRequest(Request.Method.POST, endPoint, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.e(TAG, "response: " + response); try { JSONObject obj = new JSONObject(response); // check for error if (obj.getBoolean("error") == false) { JSONObject commentObj = obj.getJSONObject("message"); String commentId = commentObj.getString("message_id"); String commentText = commentObj.getString("message"); String createdAt = commentObj.getString("created_at"); JSONObject userObj = obj.getJSONObject("user"); String userId = userObj.getString("user_id"); String userName = userObj.getString("name"); User user = new User(userId, userName, null); Message message = new Message(); message.setId(commentId); message.setMessage(commentText); message.setCreatedAt(createdAt); message.setUser(user); messageArrayList.add(message); mAdapter.notifyDataSetChanged(); if (mAdapter.getItemCount() > 1) { // scrolling to bottom of the recycler view recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, mAdapter.getItemCount() - 1); } } else { Toast.makeText(getApplicationContext(), "" + obj.getString("message"), Toast.LENGTH_LONG).show(); } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "json parse error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { NetworkResponse networkResponse = error.networkResponse; Log.e(TAG, "Volley error: " + error.getMessage() + ", code: " + networkResponse); Toast.makeText(getApplicationContext(), "Volley error: " + error.getMessage(), Toast.LENGTH_SHORT).show(); inputMessage.setText(message); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<String, String>(); params.put("user_id", MyApplication.getInstance().getPrefManager().getUser().getId()); params.put("message", message); Log.e(TAG, "Params: " + params.toString()); return params; }; }; // disabling retry policy so that it won't make // multiple http calls int socketTimeout = 0; RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT); strReq.setRetryPolicy(policy); //Adding request to request queue MyApplication.getInstance().addToRequestQueue(strReq); } /** * Fetching all the messages of a single chat room * */ private void fetchChatThread() { String endPoint = EndPoints.CHAT_THREAD.replace("_ID_", chatRoomId); Log.e(TAG, "endPoint: " + endPoint); StringRequest strReq = new StringRequest(Request.Method.GET, endPoint, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.e(TAG, "response: " + response); try { JSONObject obj = new JSONObject(response); // check for error if (obj.getBoolean("error") == false) { JSONArray commentsObj = obj.getJSONArray("messages"); for (int i = 0; i < commentsObj.length(); i++) { JSONObject commentObj = (JSONObject) commentsObj.get(i); String commentId = commentObj.getString("message_id"); String commentText = commentObj.getString("message"); String createdAt = commentObj.getString("created_at"); JSONObject userObj = commentObj.getJSONObject("user"); String userId = userObj.getString("user_id"); String userName = userObj.getString("username"); User user = new User(userId, userName, null); Message message = new Message(); message.setId(commentId); message.setMessage(commentText); message.setCreatedAt(createdAt); message.setUser(user); messageArrayList.add(message); } mAdapter.notifyDataSetChanged(); if (mAdapter.getItemCount() > 1) { recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, mAdapter.getItemCount() - 1); } } else { Toast.makeText(getApplicationContext(), "" + obj.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show(); } } catch (JSONException e) { Log.e(TAG, "json parsing error: " + e.getMessage()); Toast.makeText(getApplicationContext(), "json parse error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { NetworkResponse networkResponse = error.networkResponse; Log.e(TAG, "Volley error: " + error.getMessage() + ", code: " + networkResponse); Toast.makeText(getApplicationContext(), "Volley error: " + error.getMessage(), Toast.LENGTH_SHORT).show(); } }); //Adding request to request queue MyApplication.getInstance().addToRequestQueue(strReq); } }
حالا برنامه را برروی دو دستگاه مختلف نصب کنید و ارسال یک پیام در اتاق چت را امتحان کنید. دستگاه دیگر باید شروع به دریافت پیام کند. شما همچنین میتوانید با استفاده از پنل مدیریت http://localhost/gcm_chat اقدام به ارسال پیام به هر دو دستگاه کنید. و همچنین شما میتوانید برنامه را به اندازهی حداقل برسانید(minimize) و پیام جدید را در نوار اعلانات(notification bar) بررسی کنید.
امیدواریم از قسمت سوم مقاله ساخت اپلیکیشن فایربیس و PHP بهره کافی را برده باشید. امیدواریم در ادامه با مطالب ساخت اپلیکیشن چت با فایربیس همراه ما باشید.