اذهب إلى المحتوى

لوحة المتصدرين

  1. محمد فواز عرابي

    • نقاط

      1

    • المساهمات

      71


  2. Mohamed Salah65

    Mohamed Salah65

    الأعضاء


    • نقاط

      1

    • المساهمات

      7


  3. imad sadik

    imad sadik

    الأعضاء


    • نقاط

      1

    • المساهمات

      1


  4. Adil Elsaeed

    Adil Elsaeed

    الأعضاء


    • نقاط

      1

    • المساهمات

      28


المحتوى الأكثر حصولًا على سمعة جيدة

المحتوى الأعلى تقييمًا في 05/01/17 في كل الموقع

  1. الإصدار 1.0.0

    47177 تنزيل

    يضع هذا الكتاب المُوجز القارئ على أعتاب عالم تصميم تجربة المُستخدمين UX، وهو علم له قواعده وأصوله وأدواته، ويهدف إلى تعريف القارئ المُبتدئ بأساس هذا العلم وكيف يُطبّق على المُنتجات الرّقمية من مواقع ويب خدميّة وتطبيقات على الأجهزة الذّكية وصولًا إلى التّصميم الأمثل الّذي يُوفِّق بين هدف المُستخدم أوّلًا وهدف الخدمة التّجاريّ، الأمر الّذي يعني منتجًا ناجحًا. يبدأ الكتاب بشرح مفاهيم عامة عن تجربة المستخدم ليواصِل مع شرح كيفية إجراء مختلف الدراسات التي يحتاج المصمِّم للقيام بها، ومتطلباتها، ثم الأمور الواجب أخذها بالحسبان عند التصميم لضمان تجربة استخدام مريحة وممتازة، ليختتم في النهاية بالإشارة إلى أهمية الإحصائيات وضرورة الاعتماد عليها، حيث خُصّصت عدة أقسام لهذه النقطة، لتشير إلى مدى أهمية اعتماد بيانات وإحصائيات المستخدمين مثل أساس للتصميم، وكذا أبرز الإحصائيات الممكن التحصل عليها من خلال عدة اختبارات. يمكنك قراءة فصول هذا الكتاب مباشرةً على شكل مقالات، وإليك العناوين: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم
    1 نقطة
  2. يفتقر تطبيق الاقتراعات الذي نعمل على إنشائه إلى آلية جيّدة للتصويت على الأسئلة التي يتم عرضها للمستخدم، لذا يجب علينا توفير استمارة تتيح للمستخدم التصويت على الإجابة التي يرغب بها. إضافة إلى ذلك، سنتعرف في هذا الدرس على العروض العامة التي تهدف إلى اختصار الوقت والجهد عبر التخلّص من العمليات المتكررة بصورة دائمة، مثل جلب البيانات من قاعدة البيانات وعرض النتائج في صفحة مستقلة. وفي نهاية الدرس سنتعرف على الملفات الساكنة وسنضيف بعض التنسيقات إلى تطبيق الاقتراعات. إنشاء استمارة بسيطة يفترض أن يكون المستخدم قادرًا على اختيار إحدى الإجابات الخاصة بسؤال معين، ولكن قالب detail بشكله الحالي لا يوفّر هذا اﻷمر، لذا سنحتاج إلى إضافة استمارة إلى هذا القالب. توجّه إلى الملف polls/detail.html في مجلد القوالب templates ثم أضف إليه الشيفرة التالية: <h1>{{ question.question_text }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> {% endfor %} <input type="submit" value="صوّت" /> </form> يعمل السطر الأول على جلب نص السؤال ووضعه داخل وسم <h1> لعرضه بشكل واضح وكبير. في السطر الثاني يتم التحقق مما إذا كان المتغير error_message يحمل قيمة أم ﻻ، فإن كان كذلك يتم عرض هذه القيمة وإلا فلا، سنستخدم هذا المتغير بعد قليل في العرض المسؤول عن التصويت، حيث سيحمل هذا المتغير قيمة نصية وهي عبارة عن رسالة نخبر المستخدم فيها أنّه لم يقم باختيار أي إجابة من الإجابات المعروضة. في السطر الثالث أنشأنا استمارة يتم توليد قيمة الحدث action فيه بصورة ديناميكية، وذلك باستخدام الوسم url والمسار vote والمتغير question.id والذي يمثّل الرقم المعرف للسؤال، وستكون طريقة إرسال الحدث هي POST، وبما أننا نعتمد هذه الطريقة، فعلينا الانتباه إلى حماية المعلومات من تزوير الطلب عبر المواقع Cross-site request forgery والمعروف اختصارًا بـ CSRF. ولكن لا تقلق، يوفّر Django نظامًا سهل الاستخدام لتجنّب هذه المشكلة. باختصار، يجب عليك وضع الوسم {% csrf_tocken %} في أي استمارة تعتمد طريقة POST ﻹرسال اﻷحداث. تعمل الحلقة for على إضافة زر اختيار Radio button إلى الإجابات الخاصة بالسؤال الذي اختاره المستخدم، ويتم جلب هذه الإجابات من باستخدام العبارة question.choice_set.all. والآن لنقم بإنشاء العرض الذي سيتحكم في البيانات المرسلة إلى الخادوم. أعتقد أن سير العمل في Django قد أصبح واضحًا في هذه المرحلة، ففي البداية نقوم بتعريف المسار، ثم نربط هذا المسار بالعرض الذي سيكون مسؤولًا عن القيام بأي شيء نرغب به، وبعد ذلك نربط هذا العرض بالقالب الذي سيظهر النتائج. وقد قمنا فعلًا بالخطوة الأولى من سلسلة الخطوات هذه في الدرس الرابع من هذه السلسلة عندما بدأنا الحديث عن المسارات، حيث يتضمن ملف polls/urls.py المسار التالي: url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote') هذا المسار مرتبط بالعرض view والذي قمنا بإنشائه في الدرس نفسه: def vote(request, question_id): return HttpResponse("أنت تصوت على السؤال %s." % question_id) لنقم الآن بكتابة الشيفرة المسؤولة عن عملية التصويت ضمن هذا العرض، لذا توجّه إلى ملف views.py وأضف الشيفرة التالية في بداية الملف: from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Choice, Question ثم عدّل دالة vote لتصبح بالشكل التالي: def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', {'question':question, "error_message": "لم تقم بالتصويت على السؤال"}) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) لنتكلم عن الشيفرة السابقة بشيء من التفصيل، فهناك عدد من الأمور الجديدة فيها: request.POST هو عنصر شبيه بالقاموس يتيح الوصول إلى البيانات المرسلة من الاستمارة بواسطة اسم المفتاح، وفي حالتنا هذا فإن request.POST['choice'] سوف يرجع قيمة المعرّف الخاص بالجواب الذي اختاره المستخدم، وتكون هذه القيمة على هيئة سلسلة نصية. وجدير بالذكر أن Django يقدّم عنصرًا آخر باسم request.GET ويستخدم عندما تكون طريقة إرسال البيانات في الاستمارة هي GET. يطلق العنصر request.POST['choice] خطأً مفتاحيًا KeyError وهذا النوع من الأخطاء يُطلق عندما لا يكون المفتاح المطلوب (ضمن القاموس) موجودًا ضمن مجموعة المفاتيح المتوفرة، وفي تطبيقنا ينطلق هذا الخطأ عندما ﻻ يختار المستخدم أي إجابة من الإجابات المعروضة أمامه، ويعمل على إعادة المستخدم إلى صفحة التصويت مرة أخرى، ولكن هذه المرة مع إضافة العبارة: “لم تقم بالتصويت على السؤال” إلى المتغير error_message، وبعد أن أصبح هذا المتغير يحمل قيمة معينة، سيقوم قالب detail بعرض الرسالة في المكان المناسب. إن تمت عملية التصويت بنجاح يتم إضافة صوت واحد إلى الأصوات الخاصة بالإجابة المختارة من قبل المستخدم، وقد استخدمنا الصنف HttpResponseRedirect بدلًا من الصنف HttpResponse لإعادة توجيه المستخدم إلى صفحة النتائج. هذا الصنف في الواقع هو أحد الأصناف الفرعية للصنف HttpResponse ويأخذ معاملًا واحدًا وهو عنوان URL الذي ستتم إعادة التوجيه إليه. والهدف من استخدام هذا الصنف هو تجنّب إرسال البيانات مرتين في حال ضغط المستخدم على زر الرجوع في المتصفح، وينصح باستخدام هذا الصنف في كل مرة يتم فيها التعامل بنجاح مع بيانات POST، وهذا الأمر ليس خاصًا بـ Django فقط، بل هو من الممارسات الجيدة في مجال تطوير الويب باستخدام أي لغة برمجية. استخدمنا الدالة reverse() في مشيّد الصنف HttpResponseRedirect لنتجنّب الإدخال اليدوي للمسار الذي نرغب بإعادة توجيه المستخدم إليه. يأخذ المعامل الأول في هذه الدالة اسم العرض أو اسم نمط URL المطلوب إعادة التوجيه إليه. في هذا المثال سيتم توجيه المستخدم إلى المسار الذي يحمل اسم polls:results، ولكن هذا المسار يتضمن متغيّرًا، لذا سنخبر Django بأن يأخذ قيمته من المتغيّر question.id وذلك من خلال المعامل args. بمعنى أنّه لو اختار المستخدم السؤال الذي يحمل الرقم 3، فإن الصنف HttpResponseRedirect سيعيد السلسلة النصية التالية: polls/3/results/ حيث أن قيمة question.id في هذه الحالة هو 3. بعد أن يقوم المستخدم بالتصويت على سؤال معين، تعمل دالة العرض vote() على إعادة توجيهه إلى صفحة النتائج الخاصة بذلك السؤال، فلنقم إذًا بكتابة دالة العرض الخاصّة بالنتائج. توجّه إلى ملف polls/views.py وعدّل الدالة results لتصبح بالشكل التالي: def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question}) والآن، قم بإنشاء القالب المسؤول عن عرض النتائج. في مجلد templates/polls/ أنشئ الملف results.html، وأضف إليه الشيفرة التالية: <h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">صوّت مرة أخرى</a> والآن توجّه إلى العنوان التالي في متصفحك بعد تشغيل الخادوم الخاص بـ Django: 127.0.0.1:8000/polls/1/ وستحصل على نتيجة مشابهة لهذه: وبعد التصويت سيتم توجيهك إلى صفحة النتائج: وفي حال عدم التصويت على أي إجابة والضغط على زرّ التصويت، تظهر رسالة الخطأ بالشكل التالي: العروض العامة Generic Views لنلق نظرة على ملف views.py بعد إضافة جميع الدوال إليه: from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Choice, Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] return render(request, 'polls/index.html', {'latest_question_list': latest_question_list}) def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question}) def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', {'question':question, "error_message": "لم تقم بالتصويت على السؤال"}) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) هل لاحظت أمرًا ما؟ الدالتان detail() وresults() متشابهتان كثيرًا، وفي الواقع الفارق الوحيد بينهما هو القالب الذي يرتبط بكل دالة. إضافة إلى ذلك فإن هاتين الدالتين إضافة إلى دالة index() تقوم بعمل واحد وبسيط، وهو عرض مجموعة من المعلومات على المتصفح. تمثل هذه العروض حالة شائعة جدًّا في عملية تطوير الويب، وهي الحصول على البيانات من قاعدة البيانات بالاستناد إلى معامل يتم تمريره في عنوان URL في المتصفح، ثم تحميل قالب معين، ثم تصيير ذلك القالب على متصفح الإنترنت. لقد ذكرنا في بداية هذه السلسلة أنّ إطار العمل Django يركّز على مبدأ عدم التكرار، وقد رأينا هذا بشكل واضح عند استخدامنا للدوال المختصرة render() و get_object_or_404(). كذلك يوفّر Django في حالتنا هذه طريقة مختصرة للتعامل مع هذه العمليات الشائعة، وهي العروض العامّة. تعمل هذه العروض على اختصار الأنماط المتكررة من الشيفرات إلى درجة لا تعود فيها بحاجة إلى كتابة شيفرة بايثون لكتابة التطبيق. ولاستخدام العروض العامّة يتوجّب علينا إجراء بعض التعديلات البسيطة على شيفرتنا السابقة، ويمكن تلخيص هذه التعديلات بالنقاط التالية: تعديل أنماط URL. الاستغناء عن بعض العروض التي لسنا بحاجة إليها بعد الآن. كتابة عروض جديدة بالاعتماد على عروض Django العامة. تعديل أنماط URL توجّه إلى ملف polls/urls.py وعدّله بالشكل التالي: from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ] لاحظ أنّنا استبدلنا <question_id> بـ <pk> في مساري detail و results، وسنتعرف على سبب ذلك بعد قليل. تعديل العروض التعديلات التي سنجريها على العروض ستشمل دوال index(), detail(), results() أما دالة vote() فستبقى دون تغيير: from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.views import generic from .models import Choice, Question class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html' لاحظ كيف استغنينا بشكل كامل عن الدوال الثلاثة سابقة الذكر لتحلّ محلّها أصناف تحمل أسماءً مشابهة، ونظرًا لاستخدام الأصناف في هذا النوع من العروض، فإنّها تحمل اسم العروض المستندة إلى الأصناف Class based views. لقد استخدمنا نوعين من أنواع العروض العامة، وهما ListView و DetailView. يلخّص العرض اﻷول عملية عرض جميع العناصر، في حين يلخّص العرض الثاني عملية عرض التفاصيل المرتبطة بعنصر معيّن. يحتاج كل عرض عامٍّ إلى التعرّف على النموذج الذي سيتعامل معه، ويتم التصريح عن ذلك في المعامل model. إلى جانب ذلك، يطلب العرض DetailView أن يتم إسناد قيمة المفتاح الرئيسي المأخوذة من عنوان URL إلى متغير يحمل الاسم pk، لهذا السبب قمنا بتبديل question_id بالاسم الجديد في ملف المسارات urls.py. يبحث العرض DetailView بصورة افتراضية عن القالب المرتبط به في المسار <app name>/<model_name>_detail.html، وهذا يعني أنّ هذا العرض سيستخدم القالب polls/question_detail.html بشكل تلقائي. ويمكن تجاوز هذه القيمة التلقائية من خلال تعيين اسم القالب باستخدام المعامل template_name. ينطبق الأمر ذاته على العرض ListView والذي سيستخدم بشكل تلقائي القالب polls/question_list.html. بقي أن نشير إلى أنّنا كنا نزوّد كل قالب بمتغيرات السياق التي سيستخدمها، وفي مثالنا هذا كانت المتغيرات هي question و latest_question_list. عند استخدام العروض العامّة يتم تزويد العرض DetailView بالمتغير question تلقائيًا، وذلك ﻷننا نستخدم النموذج Question، حيث يستطيع Django تحديد اسم مناسب لمتغيرات السياق بناء على النموذج الذي يرتبط بالعرض العام. ويمكن تجاوز القيمة التلقائية هذه بسهولة وذلك بإسناد اسم المتغير الذي نرغب به إلى المعامل context_object_name، وبهذا نخبر Django بأنّنا نرغب باستخدام هذا الاسم بدلًا من الاسم الذي سينشئه بصورة تلقائية وهو في هذه الحالة question_list. كما يمكنك الإبقاء على الاسم الافتراضي question_list إن كنت ترغب في ذلك، ولكن هنا عليك تغيير اسم المتغير في أي مكان يرد فيه ضمن القوالب. الملفات الساكنة Static Files يبدو تطبيقنا بشعًا أليس كذلك؟ لنقم إذًا بإضافة بعض التنسيقات البسيطة إليه، ولنتعرف على مفهوم الملفات الساكنة. إلى جانب ملفات HTML التي ينشئها الخادوم، تحتاج تطبيقات الويب بشكل عام إلى بعض الملفات اﻹضافية - مثل الصور وملفات جافا سكربت وملفات CSS - لعرض صفحات الويب بشكل منسّق ومرتّب، ويطلق Django على هذه الملفات اسم الملفات الساكنة. سنبدأ أولًا بإنشاء مجلد باسم static في مجلد polls الرئيسي، ثم أنشئ داخل المجلد static مجلّدًا جديدًا باسم polls. بمعنى آخر يجب أن يكون مسار ملف CSS بالشكل التالي: polls/static/polls/style.css. كما هو الحال مع القوالب، فإن Django يبحث بنفس الطريقة عن الملفات الساكنة، واستخدام هذه المجلدات هو أمر تنظيمي يتيح لـ Django التمييز بين الملفات الساكنة الخاصة بكل تطبيق. والآن توجّه من خلال محرّر النصوص إلى ملف style.css وأضف إليه الشيفرة التالية: li a { color: green; text-decoration: none; font-size: 1.3em; } والآن توجّه قالب index.html وأضف الشيفرة التالية في بداية الملف: {% load staticfiles %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" /> يعمل الوسم load staticfiles في بداية الملف على تحميل الوسم static المسؤول بدوره عن توليد عنوان URL المطلق للملف الساكن المطلوب. والآن أعد تحميل الصفحة الرئيسية وستلاحظ أن الأسئلة قد تلونت باللون اﻷخضر. ولإضافة صورة للخلفية قم بإنشاء مجلد images في نفس المجلد الذي يحتوي الملف style.css ثم ضع فيه الصورة التي ترغب في جعلها خلفية للصفحة، وعلى فرض استخدام صورة باسم background.gif أضف الشيفرة التالية إلى ملف style.css: body { background: white url("images/background.gif") no-repeat right bottom; } أعد تحميل الصفحة الرئيسية، وستلاحظ أن الصورة قد أصبحت خلفية للصفحة. من الجدير بالذكر أنّه لا يمكن استخدام الوسم {% static %} استخدام داخل الملفات الساكنة والتي لا يتم توليدها بواسطة Django، لذا يجب استخدام المسارات النسبية وليست المطلقة لربط الملفات الساكنة بعضها ببعض. ختامًا تعرّفنا في هذا الدرس على كيفية استخدام نماذج الاستبيان في Django بصورة مبسطة، واستخدمنا بعد ذلك العروض العامّة التي تختصر الكثير من الوقت والجهد، وفي النهاية تعرّفنا على مفهوم الملفات الساكنة وكيفية استخدامها في تطبيقات Django. في الدرس القادم وهو الدرس اﻷخير ضمن هذه السلسلة، سنتحدّث بشيء من التفصيل عن لوحة التحكم التي يتم إنشاؤها بصورة تلقائية مع كل مشروع في Django. المصدر: توثيقات Django
    1 نقطة
  3. قليلة هي تلك المشاريع والابتكارات التي تُحدِث نقلة نوعية في عالم البرمجيات والتقنية بشكل عام، وإن كنا سنشهد في هذه العشرية إحدى هذه المشاريع، فهو Docker بامتياز. فما هو Docker هذا؟ هذا المقال يهدف بشكل خاص إلى التعريف بمشروع Docker والفكرة التي جاء بها، وليس لكيفية البدء به (والتي ستكون في مقال منفصل إن شاء الله)، حيث لا يمكن البدء به إذا لم نفهم فكرته وأهميته في عالم البرمجيات. لمن هذا المقال؟لمدراء الخواديم (SysAdmins) بالدرجة الأولىللمبرمجين ومطوري الويب بالدرجة الثانية قبل أن نتحدث عن ماهية Docker أحب أولا ذكر أهم 03 مشاكل نواجهها حاليا كمطورين ومدراء خواديم والتي يحاول Docker معالجتها... 1. جحيم الاعتماديات (the dependecy hell)الأغلبية الساحقة من مشاريع الويب "الجدية" والكبيرة لا تكون قائمة بذاتها، بل هي عبارة عن مكتبات تتواصل فيما بينها أو تعتمد على مكتبات أخرى من أجل أن تعمل، مطوروا Ruby، Python وحتى node.js و php يفهمون ذلك جيدا، وكل من هذه اللغات حاول حل المشكلة على نطاقه الخاص عبر إنشاء أدوات مساعدة تعرف بـ"مدراء الاعتماديات" أو "مدراء حزم" (dependency/package managers) أمثلة ذلك: pip بالنسبة لبايثونgem بالنسبة لروبيnpm بالنسبة لـ node.jsوأخيرا محاولة متواضعة من php عبر composer..وقد تذهب اللغة إلى أبعد من ذلك مثل ما فعلت بايثون بإنشاء بيئات وهمية تعزل فيها المكتبات والاعتماديات مثل مشروع virtualenv. كل من هذه اللغات تستعمل مدراء الاعتماديات/الحزم لجلب وإدارة المكتبات الخارجية التي يعتمد عليها المشروع (third party librairies)، لكن السؤال هنا، إذا كانت هذه اللغات وفرت هذه الأدوات، فأين المشكل؟ في الحيقيقة بالرغم من توفر هذه الأدوات، تبقى هناك مشاكل عالقة دائما خاصة على مستوى تنصيب المشروع على الخواديم (deployment) من ذلك: كثرة الاعتماديات، مما يأخذ وقتا في التنزيل، وأحيانا لا توجد تلك الاعتمادية على منصة الخادوم البعيد وقد تحتاج إلى عمل تجميع يدوي لها، وهذا يجعل الأمر مُتعبا.اعتمادية تعتمد في حد ذاتها على اعتمادية أخرى، قد تكون مثلا تشترط منصة Java بأكملها، وقد تكون أيضا هذه الأخيرة تعتمد على اعتماديات أخرى وهكذا...تعارض الاعتماديات، وجود إحداها يتطلب غياب الأخرى واستحالة عملهما معا في نفس الوقتنسخة الاعتمادية التي تم تنزيلها مغايرة للتي تم تطوير المشروع بها. والنسخة الجديدة تأتي بتغيرات غير متوافقة رجعيا (API backward compatibility)كل هذه المشاكل وغيرها، تجعل من ناقلية المشروع من مكان إلى آخر أو من خادوم إلى آخر، عملية شاقّة. 2. صعوبة نشر، تنصيب، نقل مشاريع الويبمشاريع الويب الكبيرة تحتاج إلى عدد من الاحتياطات: متغيرات بيئة (environment variables) يجب تعيينها مسبقاإعداد لنظام الـ cacheإعداد للخادوم nginx أو غيرهإعدادات أمنيةتحديث النظام وبرمجياته قد يسبب عطلا ما للمشروع،...ماذا عن نقل المشروع من خادوم إلى آخر؟ أو في حالة وجود أكثر من خادوم... وكل منه له نظامه الخاص (من CentOs إلى Ubuntu مثلا أو حتى BSD).... 3. التطوير، التسليم والزرع المتواصل للمشاريع ( continuous delivery/integration)مشاريع الويب الجدية تتبع دورة تطوير معينة، تحسين دائم، إدخال لميزات جديدة بشكل دوري، قفل للثغرات ... إلخ، بعد هذا يجب إجراء سلسلة من الفحوصات (unit tests مثلا) للتأكد من سلامة المشروع وأنه أهل للمرحلة الإنتاجية منه (production ready)، كل هذا سهل محليا،... لكن عملية إبقاء جميع الخواديم مسايرة لوتيرة التطوير هذه أمر شاق ومتعب، خاصة حينما نحتاج إلى تحديث الاعتماديات أو وجود إعتماديات جديدة وبالتالي احتياطات جديدة... هذه المشاكل ليس حصرا، فهناك غيرها من المشاكلة الأمنية (مثال ثغرة في مشروع الويب تمكن المخترق من الوصول إلى النظام)، مشاكل عزل (قاعدة البيانات في نفس بيئة التطبيق نفسه مما قد يسمح للوصول السهل لها من قبل المخترق)، مشاكل نسخ اختياطي (backups)،.. الخ إذا ما هو Docker؟Docker عبارة عن أداة جديدة تستغل ميزات الإصدارات الأخيرة من نواة Linux الخاصة بعزل المهام والعمليات (processes)، عمليات الإدخال والإخراج (i/o)، حجز الذاكرة وتحديدها، صلاحيات القراءة والكتابة للقرص الصلب... وغير ذلك، في إنشاء حاويات (containers) ركز على هذه الكلمة جيدا، حيث أن هذه الحاويات تلعب دور غلاف حاوي لتطبيق ما (مشروع ويب مثلا)، بحيث يصبح قائما بذاته، مكتفٍ ذاتيا. أي أن مشروع الويب وكامل الاعتماديات التي يحتاجها ليعمل + التوزيعة المناسبة له (Fedora, Ubuntu.. الخ) بجميع التهيئات ومتغيرات البيئة التي يحتاجها، كل هذا في حاوية (قد تكون حاوية واحدة أو عدة حاويات تتخاطب في ما بينها عملا بمبدأ "عزل الاهتمامات" SoC). لتقريب الصورة، تخيل أنه باستعمال Docker يمكنك عمل التالي: طورت تطبيقك على Ubuntu أو تعلم أنه يعمل بشكل جيد على توزيعة Ubuntu وبالتحديد الإصدارة 14.04 منها، وبالتالي تقول لـ Docker استعمل نسخة Ubuntu 14.04 (يتكفل هو بتزيل الصورة الخام من Ubuntu 14.04 - حوالي 60 إلى 300 Mb تقريبا- واستعمالها كتوزيعة للحاوية)مشروع الويب خاصتي يستعمل Python وبالتحديد النسخة 2.7 منها، ويعمل بشكل جيد على هذه النسخة، لم أجرب على Python 3، تقول لـ Docker نصب Python2.7 في الحاوية التي بها نظام Ubuntu 14.04 الذي سبق تنزيل صورته الخاممشروع الويب خاصتي يستعمل مكتبة Flask أو Django و مكتبة Numpy الخاصة ببايثون، يتم تثبيتهم عبر pip وبالتالي تقول لـ Docker نصب لي على الحاوية التي بها Ubuntu 14.04 و Python 2.7 السابقة تطبيق Pip عبر مدير حزم Ubuntu (أي apt-get install python-pip) ثم باستعمال pip نصب لي مكتاب بايثون السابق ذكرهامشروع الويب الخاص بي يحتاج قاعدة بيانات MySQL، نصبها يا Docker عبر مدير حزم Ubuntuأيضا نحتاج nginx أو apache، كذلك تطلب من Docker أن ينصبه عبر مدير حزم Ubuntuاجمع لي كل هذا في صورة واحدة (image) يمكن تشغيلها على أي خادوم أو حاسوب به Docker عبر الأمر: docker run my_imageجميع هذه الخطوات يمكن أتمتتها وسردها في ملف واحد اسمه DockerFile، كل سطر من هذا الملف عبارة عن أمر لـ Docker يقوم به (تماما كما قمنا به أعلاه)، مجموع الأوامر يكون الخطوات التي يمر بها Docker لبناء الحاوية التي نريدها، في ما يلي مثال عن ملف DockerFile: FROM ubuntu 14.04 # Install Python RUN apt-get install -y python-dev # Install pip RUN apt-get install -y python-pip # Install requirements.txt ADD requirements.txt /src/requirements.txt RUN cd /src; pip install -r requirements.txt # Add the Flask App ADD . /src # EXPOSE PORT EXPOSE 80 # Run the Flask APP CMD python src/app.pyيمكن حفظ هذه الأوامر في ملف باسم Dockerfile، يمكنك أيضا تشغيل هذا الملف لبناء صورة (image) لحاوية container من خلال Docker عبر الأمر التالي: docker build -t <your username>/my-flask-app .لاحظ النقطة في آخر الأمر، والتي تعني "استعمل ملف DockerFile الموجود في المجلد الحالي"، أما تعليمة t- بعد أمر build هي لاعطاء وسم (tag) للحاوية التي نريد بناءها. عادة من المتعارف عليه هو اعطاء اسم مستخدمك يليه "/" يليه اسم تطبيقك. مفهوم الحاويات (Containers)الحاوية في Linux عبارة عن غلاف يطبّق مجموعات من القيود لعزل عملية أو مجموعة عمليات (processes) عن باقي مهام وعمليات النظام من ناحية السياق (context)، الذاكرة RAM، القراءة والكتابة، الشابكة (Network)... الخ، بحيث تكون في معزل تام عن باقي الـ processes في النظام. أي نفس فكرة الـ sandboxing. مفهوم الحاويات ليس جديدا، فقد بدأ بالظهور منذ أواخر عام 2007، حين عرض مهندسون لدى Google مشروع cgroups (اختصارا لـ Control Groups) ليتم دمجه في نواة Linux، منذ ذلك الوقت cgroups كان اللبنة الأساسية لعزل الموارد وكبحها في نظام Linux على مستوى النواة. بعدها جاء مشروع LXC (اختصارا لـ LinuX Containers) والذي جمع بين cgroups وميزة عزل نطاقات الأسماء (namespace isolation) في نواة Linux، لتوفير إمكانية إنشاء مجموعات منفصلة عن بعضها من العمليات (process groups)، كل مجموعة مكتفية ذاتيا و/أو محدودة المواد، بحيث لا تدري كل مجموعة عن الأخرى بالرغم من أنهم يشاركون نفس النواة (نواة Linux). هذه المجموعات عُرفت باسم الحاويات Containers. على الرغم من أن LXC الأقرب لفكرة Docker، إلا أنه بقي على مستوى منخفض ولم يوفر واجهة برمجية سهلة للمطورين مثل ما قدمه Docker. إضافات Dockerفي أيامه الأولى، اعتمد مشروع Docker على LXC وبنى عليه، أي استعمله كـ backend أو driver، (لكن الآن يمكن استعمال بدائل لـ LXC) لكنه أضاف عدة أمور عصرية، بعضها مستوحى من أنظمة إدارة النسخ (Version Control Systems) من ذلك: مفهوم Docker image لبناء لقطات من الحاويات (نفس فكرة ملفات .iso) يمكن تصديرها واسترادها، وبالتالي يمكن بناء صورة للمشروع بأكمله ثم يكفي استراد تلك الصورة على الخاودم، يتوجب فقط أن يكون Docker مُنصّبا.إمكانية عمل "إيداع" (Commit) للتغيرات التي قمت بها على صورة المشروع الخاص بك، وبالتالي إمكانية الرجوع للوراء في حال الخطأ، هذه الميزة تسمح أيضا لـ Docker أن يكون وسيلة حفظ احتياطي "backup".كل صورة بنيتها يمكنك مشاركتها مع المجتمع في فهرس docker، والذي كان اسمه Docker index ثم تحول مؤخرا إلى docker hub. ستجد فيه مثلا صور لـتطبيق WordPress منصبة على Ubuntu جاهزة للاستهلاك المباشر، أليس هذا رائعا؟مفهوم الـ Dockerfile الذي سبق عرض ماهيته، وهو وسيلة لأتمتة وسرد خطوات بناء مشروعك أو حاويتك. الأتمتة التي تتيحها ملفات Dockerfiles تسمح بانتهاج نسق Continuous integration الذي تكلمنا عنه، تخيل أنك تريد تجربة مشروعك على Python 3 مثلا، كل ما عليك هو تغيير نسخة Python في ملف Dockerfile وبناء حاوية جديدة لتجرب عليها الناتج، فإن كان جيدا تدفعه للخادوم، وإن لم يكن كذلك تحذفه. نفس الشيء مع إصدارات المكتبات التي يعتمد عليها مشروعك.هذه بعض مما أضافه Docker لما هو موجود، وهي وحدها كفيلة بإحداث قفزة وتحول نوعي في طريقتنا لبناء ونشر البرامج. لكن السؤال الذي قد يطرحه البعض: أليس ما يعمله Docker هو نفسه ما تعمله الآلات الافتراضية Virtual Machines ومحاكاة الأنظمة؟ باختصار، نعم ولا وسنلخص الأمر في النقاط التالية: Docker أخف بكثير من الآلات الافتراضية، يمكنك تشغيل العشرات من الحاويات في حاسوب عادي واحد، في حين لا يمكنك تشغيل 3-4 آلات افتراضية في حاسوب عادي واحد ولو كان قويا نسبيا (يثقل النظام). أي أن Docker يستهلك موارد أقل بكثير.الآلات الافتراضية تقوم بمحاكاة كـــامــل النظام وكل ما فيه ووضعه فوق النظام المضيف، في حين Docker يتشارك النواة (Linux kernel) مع النظام المضيف.ما يقوم به Docker هو تنزيل التوزيعات فقط، أي الـ bins/libs لكل توزيعة وفقط، في حين يتشارك النواة مع النظام المضيف ولا يُنزّل نواة جديدة مع تلك التوزيعة. تلك الـ bins/libs كفيلة لمحاكاة بيئة التوزيعة المرجوة، أما النواة فهي متشابهة بين جميع التوزيعات وبالتالي يتقاسمها مع النظام المضيف (لهذا Docker لا يعمل إلا على Linux).Docker يعزل التطبيق واعتمادياته فقط، في حين الآلات الافتراضية تعزل كامل النظام وما فيه من تطبيقات.الصورتان التاليتان توضحان الفرق بين Docker والآلات الافتراضية: من خلال الصورتين يمكن ملاحظة أن وزن Docker على الخادوم بشكل عام أخف بكثير من الثقل الذي تحدثه الآلات الافتراضية. الجدير بالذكر أيضا أن القائمين على مشروع Docker قاموا بتوفير ما يسمى بـ Docker Hub، يمكن فيه مشاركة صور (images) لمشروعك (إن كان مفتوح المصدر)، تجد فيه صورا للحلول مفتوحة المصدر المعروفة، كصور Wordpress, joomla, mysql, nginx .. الخ، بحيث يمكنك استهلاكها مباشرة أو استيرادها والبناء عليها أدعوك ﻷن تلقي عليه نظرة. يمكن أيضا أن تكون مستودعا خاصا لك، لمشاريعك التجارية غير مفتوحة المصدر / مجانية، تجلب منه وتسترد صورك الخاصة التي قمت ببنائها. إلى هنا نصل إلى خاتمة هذه المقدمة التعريفية. سنقوم في مقال لاحق إن شاء الله بشرح أساسيات Docker بشكل عملي. إذا كانت لغتك الانجليزية جيدة، فأنصحك بمشاهدة الفيديو التالية لفهم أعمق للمقال:
    1 نقطة
  4. يُتيح نظام تشغيل أندرويد طريقة محددة وبسيطة للتنقل بين المكونات الأساسية للتطبيقات والتي ذكرناها سابقًا وذلك عن طريق استخدام الـ Intents. Intents يُعتبر الـ Intent طريقة التواصل بين مكونات التطبيق المختلفة من أجل القيام بشيء ما، فهو يسمح لمكونات التطبيق (كالأنشطة Activities ، الخدمات Services وغيرها) من التفاعل مع بعضها البعض والقيام بإحدى المهام، فباستخدام الـ Intent يمكننا التنقل بين شاشات التطبيق المختلفة أو التطبيقات الأخرى فمثلًا عند الضغط على زر يقوم بفتح نشاط Activity جديد داخل نفس التطبيق وعرض صورة به، كما يسمح بتفاعل التطبيقات الأخرى المتواجدة على الهاتف مع تطبيقك، فيمكن عرض الصورة في تطبيق خارجي كتطبيق المعرض (Gallery). وعند استخدام الـ Intent في التنقل بين مكونات نفس التطبيق يسمى بالـ Explicit Intent، لذا يستخدم Explicit Intent في ربط مكونات التطبيق الداخلية ببعضها وسنتعرف من خلال الأمثلة القادمة على كيفية استخدام الـ Explicit Intents في التنقل بين الأنشطة وإرسال واستقبال البيانات بينهم. وعند استخدامه للتنقل إلى التطبيقات الخارجية يسمى بالـ Implicit Intent. المثال الأول الآن قم بفتح Android Studio وقم بعمل تطبيق جديد اسمه "Explicit Intent Example"، في هذا المثال سنقوم باستخدام زر للتنقل إلى نشاط جديد. أولًا نقوم بصنع واجهة المستخدم الخاصة بالنشاط الأول والرئيسي في الملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Welcome to The First Activity" android:textSize="21sp"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="GO!" android:id="@+id/gobtn"/> </LinearLayout> ثانيًا للاستجابة للزر عند الضغط عليه نقوم باستخدام onClickListener. package apps.noby.explicitintentexample; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { Button go; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); go =(Button) findViewById(R.id.gobtn); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here } }); } } وبداخلها سنقوم بكتابة الـ Intent المناسب ولكن قبل ذلك ينبغي أن نقوم بعمل نشاط جديد وربطه بواجهة استخدام جديدة ولعمل ذلك نضغط بالزر الأيمن على المجلد المسمى باسم الحزمة ثم اختر: New > Activity > Empty Activity قم بكتابة اسم النشاط الجديد وتأكد من وجود اسم الحزمة في المكان المخصص لها كما بالصورة التالية: بعد أن يقوم البرنامج بصنع ملفات النشاط الجديد من ملف activity_second.xml سنقوم بصنع واجهة المستخدم الخاصة بالنشاط الثاني. وسنضع بداخلها TextView يعرض النص "Welcome to The Second Activity": <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Welcome to The Second Activity" android:textSize="21sp"/> </LinearLayout> والآن نعود مرة أخرى للنشاط الأول لنكمل التابع ()onClick، ولصنع Intent نقوم بعمل كائن من الصنف Intent وتمرير لدالة البناء الخاصة به النشاط الذي سننتقل منه وهو MainActivity.this وتمرير النشاط الذي نريد الانتقال إليه وهو SecondActivity.class. Intent i = new Intent(MainActivity.this,SecondActivity.class); بهذا السطر نكون قد صنعنا الهدف ولكن يتبقى أن نقوم بتفعيل هذا الهدف ليستجيب له التطبيق وذلك عن طريق التابع ()startActivity وتمرير الـ Intent الذي قمنا بصناعته إليه. لتصبح الشيفرة النهائية هي: package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { Button go; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); go =(Button) findViewById(R.id.gobtn); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this,SecondActivity.class); startActivity(i); } }); } } والآن نقوم بتجربة التطبيق على المحاكي والتأكد من قيامه بالوظيفة المطلوبة. عند النظر إلى ملف AndroidManifest.xml نجد أنه تم إضافة تعريف لعنصر جديد من النوع Activity، ولاحظ أن بدون تعريف العنصر الجديد لن يعمل التطبيق بشكل سليم ولن يتعرف نظام التشغيل عليه. <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity"></activity> </application> … المثال الثاني سنقوم في هذا المثال بإرسال بيانات من نشاط وعرضها في نشاط آخر. أولًا نقوم بصنع النشاط الجديد الذي سيتم عرض البيانات بداخله. ثانيا نبدأ بصنع واجهة المستخدم في ملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Your Name" android:textSize="21sp" android:id="@+id/editt"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send" android:id="@+id/sendbtn"/> </LinearLayout> ثم سنقوم بالاستجابة لضغط الزر كما سبق وبداخل التابع ()onClick يتم تعريف الـ Intent وحفظ البيانات المكتوبة في الـ EditText بداخله ثم استدعاء النشاط الجديد. ولإرسال البيانات داخل الـ Intent يتم ذلك عن طريق التابع ()putExtra ونمرر له عنصرين وهما المفتاح و القيمة. المفتاح هو نص نميز به القيمة التي يتم إرسالها عبر الـ Intent ويُستخدم مرة أخرى لاستعادة البيانات التي أرسلناها. والقيمة هي البيانات التي نرغب في إرسالها. package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class MainActivity extends Activity { final public static String NAME_TAG = "name"; private Button send; private EditText et; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); send =(Button) findViewById(R.id.sendbtn); et = (EditText)findViewById(R.id.editt); send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this, SecondActivity.class); String name = et.getText().toString(); i.putExtra(NAME_TAG,name); startActivity(i); } }); } } قمنا بتعريف المفتاح على هيئة نص ثابت وذلك لتسهيل استخدام المفتاح مرة أخرى عند استعادة البيانات في النشاط الآخر. ثم بعد ذلك نقوم بصنع واجهة المستخدم الخاصة بالنشاط الثاني: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, " android:textSize="21sp" android:id="@+id/hellotxt"/> </LinearLayout> ولاستقبال النص الذي أرسلناه بداخل الـ Intent يجب علينا أولًا أن نستعيد الـ Intent الذي قام بفتح النشاط باستخدام التابع ()getIntent. Intent actIntent = getIntent(); ولاستعادة النص الذي حفظناه نستخدم ()getText().toString ونمرر لها المفتاح الذي قمنا بحفظ البيانات به، ثم بعد ذلك نعرض هذا النص بداخل TextView. package apps.noby.explicitintentexample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class SecondActivity extends AppCompatActivity { private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); tv =(TextView) findViewById(R.id.hellotxt); Intent actIntent = getIntent(); String name = actIntent.getExtras().getString(MainActivity.NAME_TAG); tv.setText("Hello, " + name); } } ثم نقوم بتجربة التطبيق على المحاكي. المثال الثالث سنقوم في هذا المثال بإعادة البيانات من النشاط الثاني إلى النشاط الأول عند الانتهاء من التفاعل مع النشاط الثاني. أولًا نُنشئ تطبيقًا جديدًا به نشاطان كما في الأمثلة السابقة ونبدأ بصنع واجهة المستخدم الخاصة بالنشاط الأول. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Register" android:id="@+id/regbtn"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="NO Name" android:textSize="30sp" android:id="@+id/regtxt"/> </LinearLayout> وبداخل النشاط الأول نقوم باستدعاء النشاط الثاني عند الضغط على الزر المتواجد بالواجهة ولكن في هذا المثال لإخبار نظام التشغيل بأننا ننتظر بيانات من النشاط الآخر سنقوم بفتح النشاط الآخر بالتابع ()startActivityForResult بدلًا من التابع ()startActivity. private static final int ACTIVITY_REQUEST_CODE = 6 ; private Button reg; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); reg =(Button) findViewById(R.id.regbtn); tv = (TextView)findViewById(R.id.regtxt); reg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this, SecondActivity.class); startActivityForResult(i,ACTIVITY_REQUEST_CODE); } }); } ونمرر للتابع ()startActivityForResult الـ Intent كالمعتاد والرقم الخاص بالطلب وهذا الرقم هو أي رقم صحيح، وفائدته عند رجوع البيانات للنشاط مرة أخرى التمييز بين أي الأنشطة التي قامت بإرسال هذه البيانات. وفي هذا المثال قمنا بتعريف رقم ثابت وتمريره للتابع وعند عودة البيانات سنقوم بالتأكد من هذا الرقم مرة أخرى. والآن نقوم بصنع الواجهة الخاصة بالنشاط الآخر في ملف activity_second.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter Your Name" android:textSize="21sp" android:id="@+id/edttxt"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter" android:id="@+id/entbtn" android:layout_gravity="right"/> </LinearLayout> بعد ذلك نكتب شيفرة التحكم الخاصة بهذا النشاط. package apps.noby.explicitintentexample; import android.content.Intent; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class SecondActivity extends Activity { public static final String REG_NAME_TAG ="name" ; private EditText et; private Button enter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); et =(EditText) findViewById(R.id.edttxt); enter = (Button) findViewById(R.id.entbtn); enter.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String str = et.getText().toString(); Intent in = new Intent(); in.putExtra(REG_NAME_TAG,str); setResult(RESULT_OK,in); finish(); } }); } } فعند الضغط على الزر Enter نقوم بتسجيل النص المتواجد بداخل العنصر EditText، إنشاء Intent جديد وحفظ النص بداخله ثم استدعاء التابع ()setResult والذي يمكننا من إرسال البيانات التي قمنا بتخزينها داخل Intent إلى من قام باستدعاء هذا النشاط. ونمرر للتابع ()setResult الثابت RESULT_OK والمعرّف داخل الـ (API (android.app.Activity كما نمرر لها الـ Intent الذي قمنا بإنشائه وحفظ البيانات بداخله. أخيرًا نستخدم التابع ()finish وهو المسؤول عن غلق النشاط. لاستقبال هذه البيانات في النشاط الأول نقوم بتضمين التابع ()onActivityResult في النشاط كما يلي. package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends Activity { private static final int ACTIVITY_REQUEST_CODE = 6 ; private Button reg; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); reg =(Button) findViewById(R.id.regbtn); tv = (TextView)findViewById(R.id.regtxt); reg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this, SecondActivity.class); startActivityForResult(i,ACTIVITY_REQUEST_CODE); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if ( (resultCode == RESULT_OK) && (requestCode == ACTIVITY_REQUEST_CODE)){ String name = data.getExtras().getString(SecondActivity.REG_NAME_TAG); tv.setText(name); } } } وبداخل هذا التابع نتأكد من أن النشاط السابق تم إغلاقه بشكل صحيح ولم يحدث له مشكلة عن طريق التأكد من أن المتغير resultCode يساوي RESULT_OK التي قمنا بتمريرها للتابع ()setResult ثم نتأكد أيضًا من أن النشاط الذي يُعيد هذه البيانات له رقم طلب يساوي رقم الطلب الذي قمنا باستخدامه مسبقًا، بعد ذلك نبدأ في استقبال البيانات. ثم نقوم بتشغيل التطبيق على المحاكي. الأمثلة السابقة كانت باستخدام Explicit Intent للقيام بالوظيفة المطلوبة ولربط مكونات التطبيق المختلفة ببعضها فيمكنك من صنع تطبيق متعدد الأنشطة، في الأمثلة القادمة سنقوم باستخدام الـ Implicit Intents والتعامل معها. المثال الرابع في هذا المثال سنقوم بفتح موقع أكاديمية حسوب في تطبيق المتصفح، وبنفس الطريقة يمكن فتح الروابط الأخرى خارج التطبيق. قم بإنشاء تطبيق جديد يُدعى "Implicit Intent Example" ثم نبدأ بصنع واجهة المستخدم في ملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Open Hsoub Academy" android:id="@+id/opnbtn"/> </LinearLayout> وعند الضغط على الزر نقوم بفتح الرابط. Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://academy.hsoub.com")); startActivity(i); ونمرر لدالة البناء الخاصة بالـ Intent نوع الفعل الذي نريده من التطبيق الخارجي وفي هذا المثال نريد تطبيق يستطيع عرض البيانات بداخله، ثم نمرر له نوع البيانات المراد عرضها وهي الرابط الخاص بموقع أكاديمية حسوب. لتصبح الشيفرة النهائية الخاصة بالبرنامج كما يلي: package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { private Button open; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); open = (Button) findViewById(R.id.opnbtn); open.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://academy.hsoub.com")); startActivity(i); } }); } } ثم نقوم بتجربة التطبيق النهائي على المحاكي. المثال الخامس في هذا التطبيق سنقوم بالاتصال من خلال فتح تطبيق الاتصال من داخل التطبيق الخاص بنا. نبدأ أولًا بصنع واجهة المستخدم في ملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Dial Number" android:id="@+id/dialbtn"/> </LinearLayout> وعند الضغط على الزر نقوم بالاتصال بالرقم التالي: Intent i = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:0123456789")); startActivity(i); ونمرر لدالة البناء الخاصة بالـ Intent نوع الفعل الذي نريده وهو الاتصال بالرقم، ثم نمرر له الرقم. ثم نقوم بتجربة التطبيق النهائي على المحاكي. بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.
    1 نقطة
  5. السلام عليكم من أين أتعلم إنشاء هذه الأدوات التي في الصورة التالية وكيفية دمج مثل هذه الأدوات مع التخطيط للتحكم بها كاملاً من التخطيط ؟ https://suar.me/9ZA1 ملحوظة : لا اريد روابط لأدوات جاهزة مثل التي منتشرة على الانترنت بل أريد دليل لتعلم صناعتها من الصفر وشكرا لكم ( من كتم علماً الجمه الله لجاماً من نار )
    1 نقطة
  6. 1 نقطة
  7. أريد أن تعلم رموز جافا وأنا لا أعرف هل يمكنك مساعدتي من فضلك
    1 نقطة
  8. ربما كان خطأ من الكاتب أو يقصد Shopify لأن الكلام كان في سياق الحديث عنه، أو ربما يقصد تكاليف بوابات الدفع، فمثلا بايبال يقوم بأخذ عمولة عند الدفع عن طريقه، ويدلل على هذا قوله لان WooCommerce لا تحتاج منك إلى تسجيل أصلا.
    1 نقطة
  9. تتم الكتابة على الملفات في جافا باستخدام FileOutputStream حيث يتم إنشاء الملف في حال عدم وجوده ثم الكتابة عليه أو الكتابة عليه مباشرة في حال وجوده. أنشئ غرض object من الصف FileOutputStream والذي يحتوي على تعريفين للـ constructor الأول يأخذ معامل وهو اسم الملف المراد الكتابة عليه لإنشاء قناة output stream: OutputStream f = new FileOutputStream("C:/java/hello") المُنشئ الثاني يأخذ متغير من نوع File كمعامل argument لإنشاء قناة output stream: File f = new File("C:/java/hello"); OutputStream f = new FileOutputStream(f);في المثال التالي ستتم الكتابة على الملف الموجود في المسار: C://test/myFile.txtثم القراءة منه وعرض محتوى الملف على java console: import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileOutputStreamDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = null; FileInputStream fis = null; byte[] b = {65,66,67,68,69}; int i=0; char c; try{ // create new file output stream fos=new FileOutputStream("C://test/myFile.txt"); // writes bytes to the output stream fos.write(b); // flushes the content to the underlying stream fos.flush(); // create new file input stream fis = new FileInputStream("C://test/myFile.txt"); // read till the end of the file while((i=fis.read())!=-1) { // convert integer to character c=(char)i; // prints System.out.print(c); } }catch(Exception ex){ // if an error occurs ex.printStackTrace(); }finally{ // closes and releases system resources from stream if(fos!=null) fos.close(); if(fis!=null) fis.close(); } } }ملاحظة: في التعامل مع الملفات يستحسن استخدام try() catch() بل في جافا يجبرك أن تقوم بوضع عملية القراءة والكتابة على الملفات ضمن try() catch(). عند فتح الملف: طباعة محتوى الملف على java console:
    1 نقطة
×
×
  • أضف...