برمجة الأندرويد

برمجة تطبيق شات الدرس الرابع

<< الدرس السابق

الدرس التالى >>

انتهينا فى الدرس السابق من عمل غرف الشات والان حان الوقت للتعامل مع الشات نفسه وسنذكر السيناريو الذى سيتم الامر عليه وهو كالتالى :

يضغط المستخدم على احد الغرف يظهر لديه اخر رسائل الغرفة وصندوق الارسال بالاسفل مساحة يمكنه الكتابة فيها ثم الضغط على زر ارسال

يتم ارسال الرسالة من الأندرويد للسيرفر -> يقوم السيرفر بتخزين الرسالة فى الـdatabase ويرسل notification للمستخدم باستخدام fcm .

يتم استقبال رسالة الـ fcm من الأندرويد  -> يقوم الاندرويد باظهار اشعار ان كان التطبيق فى الخلفية او تحديث الشات فى الحال فى حال كان التطبيق مفتوحا .

وهكذا بشكل تبادلى بين الاشخاص الذين يستخدمون التطبيق وهذا هو شكل الشات الذى سنقوم بعمله

 

 

 

اى سيتمكن المستخدمون من تبادل الرسائل النصية والصور فقط واذا فهمت الدرس جيدا ستتمكن بكل سهولة تنفيذ موضوع مشاركة الملفات وارسال الصوتيات وكذلك مشاركة الموقع الحالى والفيديوهات واى شىء تريد لكننا سنبدأ اليوم بموضوع الرسائل النصية والصور وربما الرسائل النصية فقط فى هذه التدوينة .

كالعادة سنبدأ بجزء الـ API الخاص بجهة السيرفر او الاستضافة

 

الـعمل جهة السيرفر 

هذه الرسائل سوف يتم حفظها فى قاعدة بيانات السيرفر واستدعائها والاضافة اليها لذلك اول ما سنقوم به هو إنشاء جدول الرسائل ومبدئيا سيكون به هذه الأعمدة او الحقول

 

 Messages

id

room_id

user_id

user_name

type

content

 timestamp

الـ id هو id الرسالة كل رسالة تأخذه تلقائيا عند اضافتها

الـ room_id هو عباره عن id غرفة الشات  حيث ان كل الرسائل تضاف بهذا الجدول وعند الرغبة فى استدعاء رسائل احد الغرف نقوم بتمرير هذا الـ id وعمل SELECT للرسائل عندما تكون الغرفة كذا .

الـ user_id هو عباره عن id المستخدم الذى كتب الرسالة سنحتاجه بداخل الشات كما سترى لاحقا .

الـ user_name هو اسم المستخدم الذى كتب الرسالة سنحتاجه لاظهاره مع الرسالة .

الـ type هو عباره عن نوع الرسالة يمكن ان يكون نص او صورة او فيديو مثلا او موقع او مستند حيث نستخدمه نوع الرسالة عند عرضها فى الشات لتحديد شكل الrow المطلوب كما سترى لاحقا .

الـ content هو عباره عن محتوى الرسالة اذا كانت رسالة نصية فبالتأكيد يكون النص أما ان كانت صورة مثلا فيكون اسم الصورةوامتداداها  الخ.

الـ timestamp هو عباره عن الوقت والتاريخ الخاصين بالرسالة.

سأذهب الان الى phpmyadmin لإنشاء جدول الـ Messages هذا كالتالى :

ولاحظ ان الـ type عباره عن int لاننا سنقوم باعطائه القيمة 1 مثلا للنصوص والقيمة 2 للصور واذا كان هناك فيديو ستكون القيمة 2 يمكنك عملها VARCHAR ان اردت مثلا text او photo او video وايا يكن الذى قمت بعمله ستقوم بفحصه لاحقا فى Adapter الشات لتحديد شكل الـ row لكن افضل الـ Integer

الان نريد عمل الملف الذى سيقوم باضافة الرسائل لهذا الجدول add-message.php

ملف add-message.php

سنقوم باستخدام قاعدة بيانات Marei DB وسيكون كالتالى :

وطبعا كل السطور لا تختلف عن السطور المشروحة سابقا فى الدروس الماضية التأكد من وجود القيم ثم عمل query باستخدام marei db وادخال الرسائل للجدول messages سوف أقوم برفع الملف للاستضافة الان واقوم بالتجربة

نقوم الان بالتجربة

رائع نذهب لقاعدة البيانات وسنجد انه تمت اضافة الرسالة

 

الان نريد عمل ملف استدعاء يجلب الرسائل وتحديدا اخر 20 رسالة بمجرد دخول العضو الى الغرفة يمكننا عمل موضوع الـ pagination ايضا لكى يتم عرض الرسائل كلما قام المستخدم بعمل scroll للاعلى بالشات لكن لن اتطرق لهذا الموضوع الان لابقاء الامور بسيطة قدر الامكان والتركيز على الاساسيات فقط لذلك سنكتب الملف بحيث يعود لنا باخر 20 او 25 رسالة فقط من جدول الـ messages

ملف get-messages.php

نقوم بكتابة ملف لاستدعاء الرسائل اعتمادا على الغرفة حيث يأخذ ال room_id كباراميتر

نقوم الان بتجربته بالـ postman

 

 

الان لدينا api الخاص بالرسائل إضافة رسالة واسترجاع الرسائل

طبعا عند عمل add-message يجب ارسال ريكوست fcm لكن الان سنتابع جهة الاندرويد ونؤجل موضوع الfcm بعد الاندرويد .

 

الـعمل جهة الأندرويد 

نحتاج الان لعمل Activity الشات باسم ChatActivity وسيحتوى على RecyclerView التى تحتوى على الرسائل وصندوق الارسال وبالتالى نحتاج أولا لتصميم الـ rows الخاصة الـ Recycler حيث سيكون لدينا أكثر من row بأكثر من viewholder كما سنرى الان

الرسائل المرسلة

او المستخدم الحالى للتطبيق يرى رسائله بتصميم معين عند ارسال نص وهو كالتالى

وقبل أن نبدأ بتصميم الrow الاول الخاص بالرسائل المرسلة نحتاج لتصميم الخلفية وبما أن النص قد يتغير بالزيادة والنقص فلن يجدى نفعا استخدام خلفية عادية حيث ستتمدد بشكل سىء  عندما تكون الرسالة كبيرة او تحتوى على عدة سطور كالتالى :

ولهذا يجب علينا توفير الخلفية على هيئة صورة nine patch او ما تسمى بالتسع رقع واذا لم يسبق لك التعامل معها فببساطه هو عباره عن صورة png عادية نعطيها بيانات اضافية انه اذا كانت الصورة ستتمدد يجب ان تتمدد منها مناطق معينة وليست كلها تتمدد ففى الصورة السابقة مثلا نضيف للصورة معلومات انه فى حال تمددها يجب عليه تمديد الجزء المشار له بالاسود هذا فقط

فى هذه الحالة اذا تمددت الصورة لتغطى مساحة اكبر فان ما يتمدد هو الجزء المغطى باللون الاسود فقط اما الباقى فسيظل كما هو  لتصبح خلفية النص كالتالى

اعتقد ان الفكرة وصلت الان لذلك سنبدأ بالعمل فعليا و نقوم باعطاء صورة png عادية بيانات التمدد هذه

توفر لنا الـ Android SDK اداة لعمل ذلك موجودة على المسار الخاص بالـ SDK بداخل مجلد tools تسمى draw9patch

نقوم بفتحها ونقوم بعمل drag و drop للصورة الاولى التى لدينا للرسالة لنقوم بتحويلها من png عادية الى 9patch

وكما تلاحظ على اليمين يعرض لك الاشكال التى ستكون عليها الصورة كـ9patch نبدأ العمل الان والمثال الذى ذكرته سابقا كان للتوضيح لكن لو ركزت الان فى الاداة فى الصورة اليسرى ستجد ان هناك 1pixel فى كل جهة هذا هو البيكسل الذى يحمل البيانات حيث نحدد عن طريقه منطقة التمدد من الاعلى واليسار كالتالى

وبمجرد تحديد المنطقة التى ستمدد فى العرض من البيكسل الاعلى كما يظهر الخط الاسود بالاعلى يوضح المنطقة التى ستمدد فى العرض والخط الاسود على اليسار يحدد المنطقة التى ستتمدد فى الارتفاع وستجد الناتج عن هذا ظهر فى النصف الايمن من الاداة الخاص بالعرض ويتبقى الان الجانب الايمن والجانب الاسفل وهو عباره عن تحديد مكان المحتوى اى عندما تكون خلفية المحتوى الموجود بها من اين يبدأ والى اين ينتهى

ولاحظ الخط الاسود على اليمين ووالاسفل وضغط على show content لنرى كيف سيكون عرض المحتوى والان هى جاهزة للحفظ نقوم بالضغط على file -> save 9-patch ونقوم بحفظها وسنجد ان الامتداد .9png

الان نقوم بعمل صورة او خلفية الرسالة الواردة من الاشخاص الاخرين

 

ونقوم بحفظها ايضا

ويمكنك جعل التمدد نقطه واحده بالطول وبالعرض اى كالتالى

 لكن عمدت ان اجعل الخطوط عريضه والتصميم كبير فى المثال السابق  نوعا ما للتوضيح فقط .

والان نقوم بوضع الصورتين فى مجلد الـ drawables ثم نبدأ الان بتصميم الrow الاول وهو الخاص بالرسائل المرسلة وسيكون كالتالى

 

نقوم الان بتصميم ال row الخاص بالرسالة المستلمة وسيكون كالتصميم نص بالاعلى ونص بالاسفل بالاعلى اسم من ارسل الرسالة الينا وتحت الرسالة التاريخ

 

 

 

الان لدينا الصفين الخاصين بالرسالة المرسلة والرسالة المستلمة او الواردة نحتاج الان لعمل نفس الصفين لكن للصور  وستكون كالاتى

صف الرسالة المرسلة من النوع صورة

 

الصورة المستلمة من النوع صورة

 

 

اذا كنت ستقوم بدعم رسائل الفيديو فى الشات تقوم ايضا بعمل row للفيديو واذا كنت ستدعم ارسال المستندات تقوم بعمل row خاص بالمستند به اسم المستند مع ايقونة download مثلا وهكذا وكل صف او row يكون للجهتين جهة الرسالة المرسلة وجهة الرسالة القادمة او الواردة كما فعلنا مع صف النص وصف الصورة لكن سأكتفى بصف النص والصورة فى هذه السلسلة

ننتقل الان للادابتر الخاص بالشات كما قلنا فهى مجرد ريسكلر فيو بها صفوف متتابعة لذلك نقوم الان بعمل هيكلة الادابتر وبما ان الادابتر يعتمد على الـرسائل فسنقوم بعمل الموديل الخاصة بالرسالة وكذلك نوع الرسالة أولا

وسيكون كالتالى :

 

وهو عباره عن الموديل الخاص بالراجع من response الرسائل get-messages والذى يمثل  JSON كل ايتم وسنستخدمه ايضا فى الارسال والـ MessageType كالتالى :

وهى عباره عن نوع الرسالة لكى نستخدمها لاحقا نرمز لكل نوع برقم معين وهذا الرقم لا علاقة له بنوع الرسالة فى السيرفر فقط هذا الرقم داخل الاندرويد للتنظيم ولجعل الادابتر مقروءا بشكل افضل

وفى الموديل Message سوف تلاحظ ان الميثود الاخيرة getTimeStamp تعود لنا بالتاريخ الخاص بالسيرفر بتوقيت غرينتش

اى تعود لنا بالسنة واليوم والتاريخ  وطبعا عند الاستدعاء فى الشات نريد أن تكون بالوقت المحلى لذلك نكتب ميثود بداخل كلاس الـ Message  تقوم باخذ هذا التوقيت وتحويله الى التوقيت المحلى للمستخدم حسب جهازه ومنطقته ونعرض الساعة فقط كما فى تصميم الشات بالاعلى وستكون هذه الميثود كالتالى :

واذا كنت راجعت تدوينة الوقت والتاريخ فى الأندرويد والجافا فانك الان تعرف اغلب محتوى الميثود السابقة ربما الجديد فقط هو عباره عن TimeZone وهنا نستدعى المنطقة الزمنية للجهاز الحالى ونقوم بالحصول على ال rowOffset وهو الفرق بين الزمن بتوقيت غرينتش والزمن المحلى للهاتف ونقوم بتحويل التاريخ وتنسيقه الى الساعة الدقيقة الـ AM او PM وعرضه ولاحظ انه اذا كانت الـ timestamp اذا كانت بnull فسنقوم بالعودة بكلمة now وهذه الحالة عند ارسال الرسالة مباشرة من قبل المستخدم الحالى فنعرضها له فى الشات قبل حتى ان نرسلها للسيرفر وبالتالى لا يتوفر لدينا وقت السيرفر يمكننا معالجة الامر يدويا والرجوع بالتوقيت الحالى للجهاز لكن لا نريد تعقيد الامور وابقاء الامور بسيطة قدر الامكان حيث اننا هنا فى تطبيق الشات هناك عشرات الاشياء التى يمكن عملها حتى يصبح التطبيق كتطبيقات الشات الاحترافية الاخرى لكننا لن نتطرق اليها وسنكتفى بالاساسيات والتى اذا فهمتها جيدا ستطبق باقى الاشياء بسهولة وفى حالة اردت تطبيق فكرة ما فى الشات وجدتها فى تطبيقات الشات الاخرى  ولم تستطع يمكنك ترك سؤالك بجروب المطورين وسنقوم بتوضيحها لك .

 

الان نذهب للـ Adapter وسيحتوى هذا الـ Adapter على عدة Holders لكل حالة حيث هناك الرسالة المرسلة كنص والرسالة المرسلة كصورة والرسالة المستلمة كنص والرسالة المستلمة كصورة واذا كنت ستقوم بدعم انواع اخرى من الرسائل بالشات فستحتاج لعمل holder لكل حالة كما وضحنا وسأقوم بكتابة الادابتر مرة واحده ثم نشرحه جزءا جزءا

الكونستراكتور يأخذ هذا الادابتر باراميترين الاول هو عباره عن List الرسائل والثانى هو عباره عن الـ Context لاننا قد نحتاجه فى اى وقت لاحقا

قبل ان نتطرق الى باقى الMethods سنبدأ بالـ getItemViewType وهذه الميثود تقوم بتحديد نوع الـ View للـ item وتحدثنا عنها سابقا فى تدوينة الإستخدام المتقدم للـ RecylcerView  وستكون كالتالى :

اتفقنا اننا جهة السيرفر اذا كانت الرسالة نصية سنستخدم الـ type رقم 1 والذا كانت صورة سنستخدم الرقم 2 بغض النظر عن مرسلها

لذلك هنا نقوم بالحصول على الـ userID المسجل دخول حاليا لأننا نريد ان نحدد اذا كانت الرسالة مرسلة ام مستلمه واذا كان id المرسل للرسالة القادم من السيرفر يساوى الـ userID الحالى اذا فانها رسالة مرسلة  بواسطة هذا المستخدم  واذا لا فانها ارسلت بواسطة مستخدم اخر لذلك قمنا بعمل check اذا كانت الرسالة مرسلة  يدخل فى حالة if الاولى اذا كانت مستلمة يدخل فى حالة else الاولى

واذا عرفنا ان الرسالة مرسلة من قبل المستخدم الحالى بداخل الif نقوم ايضا بعمل check على الرسالة المرسلة لنعرف هل كانت نصاً ام كانت صورة اذا كانت نص اى type =1 من السيرفر فاننا نعود بالـ MessageType كـ SENT_TEXT واذا كانت صورة نعود بـ SENT_IMAGE لاننا سنستخدم نوع الرسالة فى الميثود onCreateViewHolder وكذلك فى الـميثود onBindViewHolder ونفس الامر فى الرسالة المرسلة فعلنا فى الرسالة المستلمة .

الان نقوم بعمل الـ Holders .

لدينا Holder خاص بكل نوع لكن هناك اشياء تتكرر مع كل الانواع مثلا الرسالة النصية لديها وقت يعرض فى الشات وكذلك الرسالة التى تحتوى على صورة لديها وقت  هذا فى حالة كانت رسالة مرسلة اما فى حالة رسالة مستلمة تجد انه يتكرر الاسم الخاص بالمرسل والتوقيت فى كل رسالة وايا كان نوعها لذلك نقوم بإنشاء ViewHolder رئيسى يحتوى على الاشياء المشتركة ثم نرث منه فى حالة الرسالة النصية وفى حالة الرسالة كصورة .

نبدأ بالـرسالة المرسلة وستكون كالتالى :

لاحظ ان Holder الرسالة النصية يرث من الـ Holder الاساسى وكذلك Holder الرسالة كصورة وبالتالى SentTextHolder مثلا يحتوى ايضا على ال tvTime وهو وقت الرسالة  .

 

الرسائل المستلمة ايضا ستكون كالاتى :

ولاحظ ان الـ Received Text Holder يحتوى على الـ tvTime و tvUsername لانه يرث من الـ Received Message Holder طبعا كان يمكن عمل 2 holders فقط لكل حالة لكن سنكرر نفس الكلام فى تعريف المكونات مثل tv time وفى حالة اردت تطوير الشات مستقبلا فستقوم بالنسخ واللصق كثيرا ونكرر العديد من الاشياء  وسيصبح الامر مأساة .

 

الان نذهب للميثود onCreateViewHolder والمسؤولة عن انشاء الـ View لنقوم بكتابة الكود لتصبح كالتالى :

اى نقوم بعمل holder حسب نوع ال view والذى يعود لنا بسبب الميثود getItemViewType التى وضحناها سابقا .

الان نذهب للميثود onBindViewHolder

نقوم بفحص الـ type وبناءا عليه نقوم بفعل ما نريد ولاحظ اننا استخدمنا مكتبة Glide لتحميل الصور فى حالة الصورة المرسلة او المستلمة طبعا هناك من يرغب  بتحميل الصور المستلمة  مثل طريقة الواتس اب ويستخدم مكتبة مثل fresco  وهى مكتبة بواسطة فيس بوك والتى توفر ميزة الـ progressive JPEG اى ان الصورة تظهر غير واضحة بسرعة ثم تضح شيئا فشيئا حتى تظهر كاملة وقد ترغب بتحميلها لجهاز المستخدم وعرضها او ترغب فى عرض الصورة المرسلة من جهاز المستخدم مباشرة الكثير من الامور قد يتم فعلها فى هذه النقطة يمكنك تطويعها وفعل ما تريد لكن الان سأكتفى باستخدام مكتبة Glide وتحميل الصور بها مباشرة كما تم امامك فى الـ Holder وبالنسبة للميثود load استخدمنا Urls.IMAGES_URL وهى عباره عن رابط الصور الذى سنقوم برفع الصور عليه عند ارفاق صورة من قبل المستخدم

طبعا هذا المسار غير موجود حاليا وسنتطرق له لاحقا ونقوم بعمل ميزة ارسال الصور فى الشات لكن الان لنتابع

الان يتبقى ميثود getItemCount نقوم الان بعملها تعود بحجم list الـ messages

الان انتهينا من الادابتر

 

نذهب الان لملف الـ API لنضيف methods ارسال واستقبال الرسائل

 

نقوم الان بإنشاء أكتيتيفى جديد باسم  Chat Activity ونقوم بتصميم الـ layout كالتالى :

ريسكلر فيو وتحتها لاي اوت تحتوى عى ايقونة الارسال وايقونة ادراج مرفق وفى المنتصف بينهما EditText ليقوم المستخدم بالكتابة فيه كعادة تطبيقات الشات الاخرى

نقوم بتعريف المكونات فى الجافا

وعرفنا الـ roomID لاننا سنحتاجها وكذلك الـ userID والـ adapter والـ List الخاصة بالرسائل التى سنستقبلها من السيرفر

الان نذهب للادابتر الخاص بغرف الشات لنجعله يرسل الـ room_id ويفتح هذا الاكتيتيفى عند الضغط على أحد الغرف كالتالى :

 

نعود الان للـ Chat Activity لنقوم بكتابة الميثود التى ستجلب الرسائل القديمة الى الشات عند فتح الغرفة اخر 25 رسالة كما اتفقنا ونفذنا فى php

نقوم باستدعاء الميثود فى الـ onCreate وإعطاء الـ LayoutManager للـ recycler

نقوم بكتابة ميثود AddMessage والتى ستستدعى فى كل مرة يتم ارسال رسالة من قبل المستخدم لاضافة الرسالة للرسائل فى السيرفر

الان نقوم بتشغيل التطبيق والتجربة

ممتاز الان يتبقى لنا ان نقوم بجعل الرسالة ترسل للشات عند الضغط على زر الارسال

فى السطر الاول المشار اليه بالاحمر فانه اذا كان صندوق النص الخاص بكتابة الرسالة فارغا فلا تفعل شىء

اما اذا لا فانه يتابع باقى الكود نقوم بسحب الرسالة المكتوبة فى متغير msg نقوم بانشاء رسالة جديدة نضع النوع = 1 اى رسالة نصية نعطى لها roomID والمستخدم الحالى userId واسم المستخدم والـ content هو عباره عن المتغير msg ثم نقوم باضافة الرسالة اولا الى List الرسائل التى تغذى الادابتر messages.add وبعد ذلك نعلم الادابتر ان هناك رسالة تمت اضافتها فى الموضع الاخير عن طري قالميثود notify item inserted ثم نقوم بعمل scroll برمجيا للريسيكلر للوصول لاخر عنصر ثم نفرغ صندوق الارسال ثم نقوم باستخدام الميثود addMessage لتضاف الرسالة الى السيرفر .

نقوم الان بتجربة التطبيق

 

الان الشات جاهز لكنه ليس فوريا بمعنى انه لو دخل صديقك الان وارسل رسالة فى الغرفة فلن تراها مباشرة الا اذا خرجت من الغرفة ودخلت مرة اخرى فيجب على الرسالة ان تظهر لك على الفور بمجرد ارسالها من صديقك وهذا ما سنقوم به فى التدوينة القادمة .

 

 

تم تحديث التطبيق على الـ githup للاندرويد وكذلك ملفات الـ api

<< الدرس السابق

الدرس التالى >>

السابق
برمجة تطبيق شات الدرس الثالث
التالي
برمجة تطبيق شات الدرس الخامس

4 تعليقات

أضف تعليقا

  1. amr zayed قال:

    ممتاز

  2. ALI قال:

    Please can you give us your cours about chat app every day…I’am waitting..thanhs

  3. Ahmed قال:

    كيفيه ارسال الصور

  4. وجدان قال:

    هل يمكنني تطبيق هذا الدرس مباشرة بدون تطبيق الدروس السابقة ؟ آم انها مرتبطة ببعض ؟ لآني عملت تسجيل دخول بطريقة مشابهه مسبقاً ولا احتاج لغرف الشات في تطبيقي .
    فهل يمكن البدء بهذا الدرس .

اترك تعليقاً

This site uses Akismet to reduce spam. Learn how your comment data is processed.