<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>پست‌های انتشارات موبایل لَبْ</title>
        <link>https://virgool.io/MobileLab/feed</link>
        <description>به موبایل لَبْ خوش آمدید! هر هفته با آموزش های جدید در حوزه برنامه نویسی موبایل در خدمت شما هستیم.</description>
        <language>fa</language>
        <pubDate>2026-06-16 06:36:14</pubDate>
        <image>
            <url>https://files.virgool.io/upload/publication/af5wvxo4gwhq/xu6z5m.png</url>
            <title>موبایل لَبْ</title>
            <link>https://virgool.io/MobileLab</link>
        </image>

                    <item>
                <title>پنج مهارت برتر برای یک برنامه‌نویس موفق</title>
                <link>https://virgool.io/MobileLab/%D9%BE%D9%86%D8%AC-%D9%85%D9%87%D8%A7%D8%B1%D8%AA-%D8%A8%D8%B1%D8%AA%D8%B1-%D8%A8%D8%B1%D8%A7%DB%8C-%DB%8C%DA%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3-%D9%85%D9%88%D9%81%D9%82-qloh4lldfz3r</link>
                <description>پنج مهارت برتر برای یک برنامه‌نویس موفقهمیشه باید به کسانی که قرار هست برنامه‌نویسی یاد بگیرند یا اینکه در مسیر یادگیری هستند بگوییم که برنامه‌نویسی فقط نوشتن چند خط کد که یاد گرفتید نیست، و یا اینکه چند دستور را پشت سرهم اجرا کنید که یک برنامه بسازید هم نیست.یک برنامه‌نویس موفق لزوما دارای مدارک آموزشی و یا تحصیلی خاص نیست. بلکه کسی هست که با سرعت بالا و دقت کامل یک محصول را ایجاد و ارائه کند.همیشه در ابتدای مسیر، کسانی هستند که ناامید و سرخورده و بی‌انگیزه می‌شوند و فکر می‌کنند که از پس برنامه‌نویسی بر نخواهند آمد ولی بعد از مدتی خیلی فعال و قوی پیش می‌روند و استعداد و ابتکار خود را به درستی به کار می‌گیرند.خب چرا اینطور شد؟ چه اتفاقی افتاد؟! حتما روی مهارت‌های خود کار کرده‌اند. ولی کدام مهارت؟!با من همراه باشید که با پنج مهارتی که یک برنامه‌نویس موفق باید داشته باشد، آشنا شوید.اگر که این مهارت‌ها را به درستی یاد بگیرید و مثل کد نوشتن به آنها اهمیت دهید، به مرور شما تبدیل به یک برنامه‌نویس خبره و موفق خواهید شد که شرکت‌های بزرگ به دنبال شما خواهند بود و به شما و مسیر شما کمک شایانی می‌کنند.بسیاری از افراد یادگرفتن کدنویسی را برتر از فرا گرفتن این پنج مهارت می‌دانند که باید بگویم برای تبدیل شدن به یک برنامه‌نویس خوب و موفق کافی نیست!۱. مهارت جستجو کردن و یا Search:مهارت سرچ کردن، اولین مهارتی هست که یک برنامه‌نویس خوب باید بداند. خیلیا فکر می‌کنند که یک برنامه‌نویس تمامی کدها و دستورات را حفظ کرده و یا آنها را یادداشت کرده و از آنها استفاده می‌کند. شاید این حرف در دهه هشتاد و نود میلادی درست بوده باشد ولی امروزه اینطور نیست.تا وقتی که اینترنت هست و گوگل چه نیازی به حفظ کردن هست!این یک موضوع بود، موضوع بعدی این هست که شما روزانه با چالش‌ها و مشکلات متعدد و جدیدی رو به رو می‌‌شوید و به‌ جای اینکه سریع بروید و در انجمن و گروه بپرسید، سریعا به گوگل مراجعه کنید اگر بعد از مدتها تلاش کردن به نتیجه نرسیدید از کسی که ممکن است به این مشکل برخود کرده باشد، سوال بپرسید و اگر باز هم نشد شما خلاقیت به خرج بدهید (رجوع به گیتهاب و یا داکیومنتهای خاص)  و تبدیل به کسی شوید که این موضوع را حل کرده است و آن را در StackOverFlow برای دیگران هم به اشتراک بگذارید.مسئله مهم این است که وقتی به مشکلی برخوردید از دنیا نبرید و فاز غم و غصه نگیرید. چیزی نیست که حل نشود چون گوگل و سرچ کردن هست و حتما هستند کسانی که قبل از شما به این مشکل برخورد کرده باشند و مهم این هست که خوب سرچ کنید. مثلا اگر Java کار میکنید، توی جمله‌ایی که در مرورگر می‌نویسید کیوورد جاوا باشد. مثلا :Java how to print message?!و یا اگر به ارور خوردید، کافیست ارور را کپی و در مرورگر جایگذاری (paste) کنید.اگر که در اول مسیر هستید و نمیدانید مشکل خود و یا خواسته خود را چگونه برای گوگل بیان کنید، کافیست به زبان نیتیو خود‌ (فارسی) در google translate بنویسید و انگلیسی آن را برداشته و به کمک نوشتن اسم زبانی که کار می‌کنید در کنار جمله به یک سری نتیجه دست پیدا کنید که ممکن است شبیه به سوال شما باشد، پس به دنبال همان تیتر خواهید بود. یعنی تیتر درست را در مرورگر کپی کنید و نتایج درست را داشته باشید.۲. آنالیز و تحلیل کردن و یا Analysis: مشکل دیگری که یک برنامه‌نویس ممکن است مواجه شود، تحلیل کردن و پیدا کردن راه‌حل هست. ممکن است syntax را کاملا بداند و با تمامی دستورات آشنا باشد ولی دقیقا نداند باید چیکار کند.منطق و لاجیک را نداند. از کس دیگری میخواهد که تحلیل را برای آن انجام دهد و فقط به عنوان یک تسک آن‌ها انجام دهد.بهتر هست مهارت تحلیل را بدانید و تسک را ریز کنید و به راه‌حل برسید. سیستم‌های دیگر و نمونه‌های مختلف را ببنید که به تحلیل و بررسی و ساختار به شما کمک کنند. که باید به مهارت اول یعنی سرچ برگردید و از آن به خوبی استفاده کنید. در اینجا شما مشکلی با زبان برنامه‌نویسی مدنظر ندارید، بلکه مشکل با پیاده‌سازی دارید، که با رجوع به نمونه‌ سورس‌های موجود روی گیتهاب و ... ، می‌توانید ایده داشته باشید.به علاوه وقتی جلوتر می‌روید و با سیستم‌های پیچیده‌تر و بزرگتر کار می‌کنید، اهمیت این موضوع بیشتر و روشن‌تر می‌شود.مثلا مهارتی مثل Database Design بسیار مهم است. پس به دنبال یادگیری انالیز و تحلیل کردن باشید.۳. خطا‌یابی یا Debugging:یکی از مهمترین مهارت‌هایی که باید به آن اهمیت دهید و آن را یاد بگیرید. هیچ سیستم و برنامه‌ایی بدون مشکل و باگ نیست. هیچ برنامه‌نویسی نیست که روزی را بدون حل کردن و فیکس‌کردن باگ و مشکل پشت سر بگذارند.پس مهم آن است که متوجه شوید که مشکل از کجا هست و آن را trace  کنید و حل کنید. به این کار خطایابی گفته می شود. مثلا در Android به کمک  Break Point ها، در html/css به کمک Inspect Element , ...! کلا به کمک Logging و خطایابی سطر به سطر می‌توانید متوجه مشکل شوید. و نکته مهم آن است که شما IDE ایی که با آن کار می‌کنید را به خوبی بشناسید و بتوانید روش دیباگینگ آن را یاد بگیرید.پس اگر به مشکل خوردید و بدون دیباگینگ به دنبال کمک گرفتن بودید، مسیر درستی را پیش نگرفته‌اید.۴. نوشتن با صفحه کلید یا Keyboard Typing:تایپ کردن سریع و استفاده از شورتکات‌ها به شما برای تسریع و سرعت بخشیدن به انجام تسک کمک می کند. یک برنامه‌نویس خوب به مرور تایپ سریع با دقت بالا را فرا می‌گیرد. یکسری سایت وجود دارند که به شما در این زمینه کمک می‌کنند.و نکته‌ی مهم تایپ کردن به زبان انگلیسی هست. دوستان برای برنامه‌نویسی یاد داشتن زبان انگلیسی برای حل کردن مشکلاتتون و خواندن داکیومنت و سرچ کردن، از نان شب هم واجب‌تر است. پس به آن اهمیت دهید.۵. کتابخانه کد یا Code Library:مهارتی است که شما به مرور با کسب تجربه کافی به آن خواهید رسید ولی چه خوب است که در ابتدای مسیر آن را یاد بگیرید.استفاده از کدهایی که قبلا نوشتید به این صورت که به شکل پکیج و یا کلاس مستقل و ماژولی می‌توانید آنها را به پروژه‌های جدیدی که ایجاد می‌کنید اضافه کنید که این کار باعث بالا بردن سرعت شما در کار می‌شود. پس همیشه به این فکر کنید که فایل و ماژول‌هایی را آماده کنید که ساختار را داشته باشید.پس به صورت یک کتابخانه‌ قابل حمل و پرتابل آنها را در جایی که نیاز دارید، اضافه کنید. اگر این مهارت را داشته باشید همیشه از دیگران جلوتر هستید.  و در آخر باید بگویم که هیچ عامل منحصر به فردی وجود ندارد که به کسی در موفقیت و رسیدن به خواسته‌هایش، کمک کند. همه چیز تناسب و درصدی هست. هیچ چیز صد‌در‌صد درست و یا غلط نیست. برای اینکه در کار خاصی و یا زندگی موفق باشید، نیاز به عوامل زیادی هست که بخشی از آنها تکنیکال و فنی هستند و بخش دیگری Soft Skills ها هستند. پس تنها راه موفقیت استفاده از تمامی امکانات موجود و یادگیری و دنبال کردن مهارت‌های مورد نیاز هست. انگیزه و تلاش را فراموش نکنید.موفق و موید باشید.سنا عبادی | جمعه ساعت ۲۲:۱۱ شب , بیست و هشت آبان ماه سال ۱۴۰۰  </description>
                <category>موبایل لَبْ</category>
                <author>سنا عبادی</author>
                <pubDate>Fri, 19 Nov 2021 22:23:52 +0330</pubDate>
            </item>
                    <item>
                <title>جت پک کمپوز، انقلابی در اندروید</title>
                <link>https://virgool.io/MobileLab/%D8%AC%D8%AA-%D9%BE%DA%A9-%DA%A9%D9%85%D9%BE%D9%88%D8%B2-%D8%A7%D9%86%D9%82%D9%84%D8%A7%D8%A8%DB%8C-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-qjgop06ubo0o</link>
                <description>چند روزیه که جت پک کمپوز استیبل شده و من از وقتی که وارد کانال بتا شد از اون استفاده کردم و قصد دارم از تجربیاتم تو این مدت بگم :) از بزرگترین و مهم ترین رویداد هایی که امسال در برنامه نویسی اندروید اتفاق افتاد، میشه به این موارد اشاره کرد:انتشار نسخه 1.5 کاتلین، که به گفته‌ی شرکت جت برینز بزرگ ترین رویداد سال ۲۰۲۱ بود!نسخه بتا و در نهایت انتشار جت پک کمپوز، فریمورکی برای طراحی یو ای به صورت declarative در اندرویداندروید استدیو نسخه Arctic fox، با قابلیت های زیادی مانند ادیتور و نمایشگر جت پک کمپوز و کلی امکانات دیگه... خب حالا جت پک کمپوز چیه؟ ی ابزار برای ساخت و توسعه یو ای به صورت declarative هست که از زبان برنامه نویسی کاتلین استفاده می‌کنه و نسبت به روش و ابزار های قدیمی برای ساخت یو ای، کد کمتری نوشته میشه و باگ های کمتری در روند ساخت یو ای به وجود میاد! اگه با فلاتر یا ری اکت نیتیو کار کردید بیشتر با مفهوم declarative آشنایی دارید! اگر با مفهوم declarative آشنایی ندارید حتما مقاله های زیر را مطالعه کنید: https://virgool.io/@leaksin/%DA%A9%D8%AF%D9%90-declarative-%DB%8C%D8%A7-%D8%A7%D8%B9%D9%84%D8%A7%D9%86%DB%8C-%DA%86%DB%8C%D8%B3%D8%AA-y2wbwbq7lsnt  https://virgool.io/devAndroid/%D9%85%D9%81%D9%87%D9%88%D9%85-declarative-ui-%DB%8C%D8%A7-%D8%B1%D8%A7%D8%A8%D8%B7-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%DB%8C-%D8%A7%D8%B9%D9%84%D8%A7%D9%86%DB%8C-n74m3jhpl9cw از کجا جت پک کمپوز رو یاد بگیرم؟برای شروع یادگیری اول مطمئن شوید که از آخرین نسخه اندروید استدیو یعنی arctic fox استفاده می‌کنید و سپس با استفاده از داکیومنت ها و کد لب هایی که در صفحه رسمی جت پک کمپوز برای شما قرار داده شده است می‌توانید یادگیری را شروع کرده و حتی اپ های قدیمی خودتون رو با اون ریفکتور کنید :) سمپل کد های کمپوز:  https://github.com/android/compose-samples  https://github.com/jetpack-compose/jetpack-compose-awesome ریپازیتوری پروژه دانشگاه خودمو هم اینجا میذارم شاید به کارتون بیاد :)  https://github.com/moeindev/InstaWeather در آخر هم توجه‌تون رو به این رشته توییت از آقای ادیب فرامرزی از تیم تپسی در مورد جت پک کمپوز جلب می‌کنم که نکات خیلی مهمی رو راجب کمپوز توضیح داده و از تجربه‌ش با کمپوز گفته: https://twitter.com/TheSNAKY/status/1416453656870805516 تجربه شخصی خودم:برای پروژه آخر ترم دانشگاه دوست داشتم که کار جدیدی انجام بدم و از این روال تکراری استفاده از دیتابایندینگ یا ویوبایندینگ خسته شده بودم تا اینکه به فکرم رسید که از جت پک کمپوز استفاده کنم که به تازگی وارد کانال بتا شده!نکته در مورد کانال های رلیز کتابخانه های گوگل:آلفا: در این فاز خیلی از APIها استیبل نیستن و ممکنه در هر نسخه به کلی عوض بشن و مجبور بشید که کل کدی که نوشتید رو بازنویسی کنید. ( تجربه ای که تو این چند وقت با نسخه کمپوز کتابخونه لاتی(Lottie) داشتم که کلا اسم متود ها و سینتکسشو عوض کرد!)بتا: تو این فاز اکثر APIهای مربوطه استیبل شده و باگ های بزرگ و اشکالات نسخه آلفا رو نداره. این نسخه معمولا بهترین وقتیه که قصد دارید یک فریمورک رو تست کنید!آرسی(RC): باگ های جزئی نسخه بتا در این نسخه رفع شده و برای رلیز نهایی استیج شده و یکبار دیگه تست میشه.نهایی: بعد از چنل RC نسخه نهایی منتشر میشه و فاصله بین RC و رلیز نهایی معمولا کمتر از یک هفته هست!طی این چند ماه تجربه لذت بخشی از کمپوز داشتم و هیچ وقت دوست ندارم که دوباره با  XML کار کنم:) تنها مشکلی که طی این چند ماه وجود داشت سر آپدیت های اندروید استدیو و کتابخونه ها بود که بعضی وقتا باهم همخونی نداشتن و یهو کل پروژه میرفت رو هوا :( اگه بازم وقت داشته باشم این مقاله رو کامل تر می‌کنم و ی سری مقاله راجب کمپوز منتشر می‌کنم پس یادتون نره انتشارات و من رو فالو کنید که اگه مطلب جدیدی گذاشتم سریعتر خبردار بشید :) </description>
                <category>موبایل لَبْ</category>
                <author>محمد معین عبدی</author>
                <pubDate>Fri, 30 Jul 2021 18:42:01 +0430</pubDate>
            </item>
                    <item>
                <title>پیاده‌سازی Deferred DeepLink‌ها</title>
                <link>https://virgool.io/MobileLab/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-deferred-deeplink-%D9%87%D8%A7-etpe5k2tuywg</link>
                <description>دو سه روز پیش توی شرکت به عنوان یه تسک فرعی، در مورد امکان پیاده‌سازی Deferred DeepLinkها توسط خودمون یه تحقیق یکی دو ساعته کردم، چون خروجیش رو وقتی برای بچه‌های شرکت نوشتم شبیه مقاله شد، گفتم اینجام بذاریم شاید برای فرد دیگه‌ای هم جذاب بود. اگر شمام نظری داشتید میتونید در قسمت نظرات بنویسید.عکس برای یه مقاله در سایت innovationm.co می‌باشد.
وقتی کاربر روی یه DeepLink کلیک میکنه، براساس اینکه App روی دستگاهش نصب باشه یا نه؟! دو حالت پیش میاد:۱- اپ (App) نصب هست: توی این حالت کاربر روی لینک کلیک میکنه و توسط App به صفحه‌ای مشخص شده توسط لینک منتقل میشه. بهش Direct DeepLink میگن.۲- اپ (App) نصب نیست: توی این حالت چون کاربر App رو نداره، با زدن روی لینک به Store منتقل میشه، و بعد از نصب، وقتی اپ رو اجرا کرد به همون صفحه مشخص شده توسط لینک منتقل میشه. لینک‌های این مدلی رو Deferred DeepLink میگن. نکته مهم اینه اینجوری نیست که اینجور لینک‌ها در ۱۰۰درصد موارد کار کنند! احتمال داره گاهی کاربر روی لینک بزنه و بعد نصب بره توی App ولی به جای خاصی منتقل نشه. (در ادامه دلیلشو میگم)هم Firebase و هم Branch.io دو حالت رو پشتیبانی میکنن. اگر بخوایم خودمون همه چیو پیاده‌سازی کنیم، حالت اول که سادس و میتونیم بدون دردسر خاصی پیاده کنیم، ولی پشتیبانی از Deferred DeepLink نیاز به کار و تست بیشتری داره. مخصوصا که یه چیز ۱۰۰درصدی نیست و همیشه باید تلاش کنی درصد مواردی که لینک کار میکنه رو بالاتر ببری.جزییات پیاده‌سازینحوه‌ی پیاده‌سازی Deferred DeepLinkها اینجوریه که وقتی کاربر روی لینک کلیک میکنه و توی مرورگر به سایت میره، اونجا یه سری اطلاعات ازش مثل مدل دیواس، آی‌پی، سایز اسکرین، سیستم‌عامل، نسخه‌سیستم‌عامل و … رو نگه میدارن. بعد وقتی کاربر اپ برای اولین‌بار اپ رو باز میکنه، دوباره همین اطلاعات رو توسط اپ به سرور میفرستن، اگر دیدن کسی با این اطلاعات قبلا روی لینکی کلیک کرده، میفهمن این همون یوزر هست و به اون صفحه‌ای که لینک مشخص کرده منتقلش میکنن. هرچقدر این Match شدن‌ها درست باشه، نشون میده کیفیت پیاده‌سازی بالاتر بوده. گاهی اوقات کاریش نمیشه کرد دیگه، مثلا کاربر روی لینک کلیک میکنه، بعد مثلا VPN وصل میکنه IPش عوض میشه، بعد میاد App رو باز میکنه!! توی این حالت خب اون Matching درست نمیشه با اینکه واقعا این همون کاربر هست. این چیزی که گفتم حالت کلی پیاده‌سازی بود. حالا با روش‌های کمکی و یا سری کانفیگ‌ها سعی میکنن درصد Match شدن رو بالاتر ببرن.بخوام یه مثال از کانفیگ بزنم، مثلا میتونه کم کردن یا زیاد کردن بازه‌ی Matching بین اطلاعات باشه، مثلا آخرین اطلاعاتم از فایریس اینه تا ۱ ساعت کاربر اگر بعد نصب App رو اجرا کنه، امکان داره Matching پیش بیاد و به صفحه خاصی که نیازه منتقل بشه. ولی بیشتر از این دیگه کار نمیکنه. این تایم برای Branch.io دو ساعت هست.بخوام یه مثال از روش کمکی بگم که به بالا رفتن Matching توی کمک میکنه (البته توی اندروید) اینه که گوگل‌پلی یه چیزی به اسم INSTALL_REFERRER داره و گوگل‌پلی میتونه بعد نصب یه سری پارامتر رو باهاش به App بده. مقاله زیر خیلی خوب پیاده‌سازی Deferred DeepLink رو با این قابلیت گوگل‌پلی توضیح داده.https://www.linkedin.com/pulse/android-deferred-deep-link-hamid-sedghi-n/ سرویس‌هایی مثل Branch.io ترکیب همه روش‌ها با هم استفاده میکنند. حتی Branch.io یه روش داره که فقط بکار سرویس‌هایی میاد که اپ‌های زیادی ازشون استفاده میکنن و اگر ما پیاده‌ کنیم خیلی سودی نبریم. در کل این دو تا مقاله Branch.io خیلی عالیه. اگر علاقمندید بخونید:https://blog.branch.io/the-importance-of-matching-accuracy-in-deep-linkinghttps://blog.branch.io/deferred-deep-linking-with-device-snapshotting</description>
                <category>موبایل لَبْ</category>
                <author>عباس اویسی</author>
                <pubDate>Thu, 04 Mar 2021 03:23:42 +0330</pubDate>
            </item>
                    <item>
                <title>تست نویسی چرا و چطور سری اول - &quot;چرا تست بنویسیم؟&quot;</title>
                <link>https://virgool.io/MobileLab/%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%DA%86%D8%B1%D8%A7-%D9%88-%DA%86%D8%B7%D9%88%D8%B1-%D8%B3%D8%B1%DB%8C-%D8%A7%D9%88%D9%84-%DA%86%D8%B1%D8%A7-%D8%AA%D8%B3%D8%AA-%D8%A8%D9%86%D9%88%DB%8C%D8%B3%DB%8C%D9%85-m2rawqsgdsds</link>
                <description>داود ی برنامه نویس اندرویده. اون تازه کار نوشتن ی امکان جدید تو برنامه رو به اتمام رسونده و صدها خط کد به کدبیس برنامه اضافه کرده. داود برنامه رو اجرا میکنه، میره به صفحه مورد نظر که تغییرش داده و… کرشبرمیگرده سر کد با تعجب دقایقی صفحه مانیتور رو نگاه میکنه ی کم میخوندش و… آها. فراموش کرده که ی متغیر رو پر کنه. ی بار دیگه اجراش می­کنه و به همون صفحه میره. ورودی رو تو editText وارد می­کنه. هوم… برنامه &quot;سود&quot; رو درست محاسبه نکرده.دوباره برمی­گرده سر کد و این دفعه debuggerو روشن می­کنه. بعد از مدتی مشکلو می­فهمه. نسبت به دفعه قبل وقت بیشتری ازش میگیره.. این دفعه برنامه درست کار میکنه.این کار یعنی اجرا، تست دستی و رفع باگ حدود 20 دقیقه ای ازش وقت میگیره.زهرا، در مقابل کارش رو به روش دیگه ای انجام میده. اون هر بار که تغییر کوچیکی تو کد میده ی unit test هم براش می­نویسه که از سالم بودنش مطمئن بشه. زهرا بعد نوشتن هر تیکه­​ ی کد تستاش رو اجرا میکنه، تست ها در عرض چند ثانیه اجرا میشن بهمین خاطر نیازی نیست زهرا زیاد منتظر بمونه تا متوجه بشه میتونه بره سراغ تیکه ی بعدی یا نه.اگه مشکلی باشه زهرا سریع متوجه مشکل میشه و حلش میکنه. حل مشکلات تو روش اون نسبت به روش داود راحت ­تره، چون فقط لازمه تو قسمت کوچیکی از کد دنبال مشکل بگرده.اون تست هاش رو تو کدبیس ذخیره میکنه تا هر بار که تغییری تو قسمت خاصی داد، مطمئن بشه تغییر جدید امکانات قبلی برنامه رو خراب نمیکنه. اینطوری دیگه لازم نیست کلی زمان برای تست دستی امکانات موجود برنامه بزاره.(چون تست های قبلی هست و زهرا اونها رو همراه تست های جدید اجرا میکنه)در مورد فهم نحوه کار سیستم هم، تست های زهرا، برای بقیه اعضای تیم زمان می خره. وقتی علی می­پرسه: &quot;وقتی X و Y ترکیب میشن سیستم چطور کار میکنه؟&quot;، داود میگه: &quot;اوممم… اینطوریه که… راستش نمیدونم بزار کدو نگاه کنم.&quot; بعضی وقتا این کارش چند دقیقه زمان میبره، ولی اکثرا نیم ساعت یا بیشتر ازش زمان می گیره. ولی زهرا میره سراغ unit-testهاش و به جواب های لحظه ای می­رسه.(تست ها مفهوم کد نوشته شده رو آسون­​ تر از خود کد می­رسونن)اجرای روش زهرا به این آسونی هام نیست و مشکلاتی داره، ولی اگه دانش تست نویسی رو داشته باشیم، میشه از پس این مشکلات بر بیایم. ی راه خوب خوندن کتاب &quot;Pragmatic Unit Testing in Java 8 with JUnit&quot; که این داستان از این کتاب برگرفته شده.این سری اول از مجموعه ی &quot;تست نویسی چرا و چطور&quot; هست. منتظر قسمت های بعدی باشید.لینک کتاب نوشته Jeff Langr</description>
                <category>موبایل لَبْ</category>
                <author>davud hosseiny</author>
                <pubDate>Mon, 21 Dec 2020 21:52:29 +0330</pubDate>
            </item>
                    <item>
                <title>منابع آموزشی در رابطه با Style و Theme</title>
                <link>https://virgool.io/MobileLab/styles-and-themes-resource-links-da30hazr87pe</link>
                <description>همیشه توی ذهنم یه عذاب وجدانی داشتم از اینکه توی Theme و Styleهای اندروید ضعیف هستم. گاهی بنظرم بدرد نخور بود و گاهی خیلی پیچیده! تا اینکه تصمیم گرفتم بطور فشرده یه هفته‌ای در رابطه باهاش تحقیق کنم. یه سری مقاله خوندم و ارائه‌های مرتبط رو‌ نگاه کردم، الان دیگه واقعا دیدم به Style و Theme توی اندروید عوض شد، قبلا چون این چیزارو بلد نبودم، برای کاستومایز کردن بعضی چیزا راه‌های سختتری میرفتم. برای شروع سعی کردم چیزهایی که یاد گرفتمو روی اپ Wood Working Tools پیاده کنم. در آینده حتما بیشتر از Theme و Style توی اپ‌هام استفاده می‌کنم. در ادامه لیست مقاله‌‌ها و ارائه‌هایی که بنظر خوب بودن رو به همراه نکات جالبشون نوشتم. اگر شمام مثل من دوست دارید بیشتر با Style و Theme توی اندروید آشنا بشید، حداقل سعی کنید همه‌ی منابع زیر رو بررسی کنید.1- Android styling: themes vs styleshttps://medium.com/androiddevelopers/android-styling-themes-vs-styles-ebe05f917578این مقاله برای شروع خیلی خوبه. در رابطه با تفاوت Style و Theme صحبت می‌کنه. یکی از بدی‌های Theme و Style این هست که برای تعریف جفتشون باید از تگ یکسان Style استفاده کرد.توی مقاله یه مثال جالب میزنه، اونم اینه که اگر از attributeهای Theme توی پروژه استفاده کنیم (بجای استفاده مستقیم از ریسورسهایی مثل رنگ و …)، شبیه این میمونه توی کدهامون بجای اینکه مستقیما از یه کلاس استفاده کنیم از اینترفیس استفاده کرده باشیم. در نتیجه میتونیم پیاده‌سازیهای مختلفی برای حالت شب و روز و … داشته باشیم.بخش Scopeش هم جالبه.2- Android styling: common theme attributeshttps://medium.com/androiddevelopers/android-styling-common-theme-attributes-8f7c50c9eabaاگر مقاله اول رو خونده باشید، متوجه شدید که چرا بهتره از Attributeهای Theme استفاده کنیم. توی این مقاله میتونید Attributeهایی که خیلی پرکاربرد هستند رو یاد بگیرید.3- Android Styling: prefer theme attributeshttps://medium.com/androiddevelopers/android-styling-prefer-theme-attributes-412caa748774این مقاله هم در مورد استفاده از Attributeهای Theme هست. تقریبا همیشه باید در همه جا از Attributeها استفاده کنید، مگه حالت استثنایی باشه که خود مقاله براش یه مثال میزنه.در مورد استفاده از Attributeها توی ColorStateList هم صحبت میکنه.4 - Themes overlayAndroid Styling: themes overlayhttps://medium.com/androiddevelopers/android-styling-themes-overlay-1ffd57745207Refactoring Android Themes with Style: Theme Overlayshttps://ataulm.com/2020/05/28/refactoring-themes-with-style-theme-overlays.htmlهمیشه این Theme Overlayها برام گنگ بود، برای رنگ دادن به Toolbar ازشون استفاده کرده بودم ولی خب خیلی درک خاصی ازشون نداشتم. مثلا نمیدونستم خودم کجا باید Theme Overlay تعریف کنم. این دو تا مقاله واقعا دید خوبی بهم داد. مخصوصا بخش Theme overlays in default styles رو توی مقاله دوم خیلی دوست داشتم، تازه فهمیدم دلیل وجود Attributeیی به اسم materialThemeOverlay چیه.5- Refactoring Android Themes with StylePart 1: Restructuring Themeshttps://ataulm.com/2020/04/30/refactoring-themes-with-style.htmlPart 2: Default Styleshttps://ataulm.com/2020/05/07/refactoring-themes-with-style-default-styles.htmlPart 3: Theme Overlayshttps://ataulm.com/2020/05/28/refactoring-themes-with-style-theme-overlays.htmlاین سه مقاله‌ هم واقعا عالی و کاربردی هستن، همه چیز کامل و با مثال توضیح داده شده. قضیه مقاله‌ها اینه که تیم توسعه Monzo بعد دیدن یه ارائه توی سال ۲۰۱۹ (لینکش در ادامه هست)، تصمیم گرفته بخش Theme و Style اپ‌شون رو ریفکتور کنه و از Best Practiceهایی که توی ارائه گفته شده استفاده کنند. فرآیندی که طی کردن و تجربیاتی که بدست آوردن توی این سه مقاله نوشته شده.مقاله سوم رو توی یه آیتم دیگه هم معرفی کردم. ولی دلم نیومد اینجا سه تاشون رو دوباره با هم نذارم. اشکال نداره شما هم دوبار بخونیدش D:6- Android themes &amp; styles demystified - Google I/O 2016https://www.youtube.com/watch?v=TIHXGwRTMWIاین فیلم چند نکته باحال یاد میده:- اولیش مهم نیست ولی برام جالب بود، این بود که فهمیدم قضیه این res-auto توی آدرس xmlns چیه. D: تاحالا نمیدونستم دلیل اسمش چیه.- دومیش اینه که یه مثال از پیاده‌سازی Style و Theme دکمه (Button) توی متریال کامپوننت میزنه که خوبه. خوبیش اینه آدم میبینه پیاده‌سازیش چطوره، بعد اگر بخواد چیزی پیاده‌سازی کنه یا حتی مهمتر مثلا رنگ و قیافه Button رو عوض بکنه، راحت میتوجه بشه باید چیارو ست کنه یا تغییر بده.- سومیش اشتباهات رایجه که آخر ارائه میگه، دیدنشون خالی از لطف نیست. توی بخش اشتباهات رایج در مورد resolve شدن Attributeها صحبت میشه. اگر دوست داشتید این مقاله هم در همین رابطه هست. برای اطلاعات بیشتر میتونید بخوندیش.https://ataulm.com/2019/10/28/resolving-view-attributes.html7- Best Practices for Themes and Styles (Android Dev Summit &#x27;18)https://www.youtube.com/watch?v=sNSlDfaNq-0یه نکته‌ی خیلی جالب این فیلم برای من همون اول فیلمه که آموزش میده چطوری رنگ‌ها رو نامگذاری کنیم. مثلا اگر دیزاینر یه رنگ جدید استفاده که غیر از رنگ‌های Primary و PrimaryVariant هست، باید با چه اسمی براش Attribute درست کنیم.وسط‌های این ارائه در مورد تغییر دادن رنگ یه دکمه صحبت میکنه، حتما ببینیدش. من قبلا‌ فکر میکردم بعضی از اینا که توی stackoverflow سوال‌های مرتبط به اینجور چیزهارو جواب میدن، چقدر خفنن! واقعا نمیدونستم اینقدر راحت میشه فهمید چطوری باید ظاهر یه دکمه کاستومایز کرد.8- Developing themes with style (Android Dev Summit &#x27;19)https://www.youtube.com/watch?v=Owkf8DhAOSoدوباره توی این ارائه در مورد این صحبت میشه که چرا باید از Attributeها استفاده کرد. این نکته رو تقریبا توی بیشتر ارائه‌‌ها یا مقاله‌ها میشه دید. اگر هنوز اینکارو نمیکنید، مثالی که توی این ارائه زده رو نگاه کنید، شاید نظرتون عوض شد و در آینده همیشه از Attributeها استفاده کردید.یه بخش مهم دیگه ارائه اونجاش هست که در مورد نامگذاری Style و Themeها صحبت میکنه. اینکه چطور اون سلسله مراتبی نامگذاریشون کنید. این نامگذاری‌ Style و Themeها همیشه برای من سوال بود تا بالاخره توی این ارائه فهمیدم قضیه‌اش چیه (توی سه مقاله ریفکتورینگ هم در این رابطه صحبت میکنه، بنظرم اونو هم بخونید). حتی توضیح میده بهتره Themeها و Styleهارو توی فایل‌های جدا بذارید.آخر ارائه در مورد یه قضیه‌ای صحبت میکنه که من تا حالا فکر میکردم کار درست رو انجام میدم چون خود اندروید استودیو هم پروژه رو اینجوری میسازه، ولی خب اشتباه بوده!! اونم این بود که میگه اسم رنگ‌هارو نباید توی فایل colors.xml یه چیزهایی مثل priamaryColor و … بذاریم. باید دقیقا اسمشو مثل indigo_500 یا indigo_200 بنویسی. نامگذاری‌های انتزاعی مثل priamaryColor برای attributeهای Theme هست.9- The Components of Material Design (Android Dev Summit &#x27;18)https://www.youtube.com/watch?v=DPH3F0v1jB0این ارائه هم هست که در مورد متریال دیزاین صحبت میکنه. اگر داکیومنت متریال رو کامل بخونید و مقاله یا فیلم‌هایی که گفتمو دیدید، بیشتر بخش‌های این ارائه براتون تکراری میشه. ولی اگر وقت دارید ببینیدش. توی یه بخشش نشون میده اگر کاستوم ویو میسازید، چطوری باید داخلش از Theme و Style که بهش ست میشه استفاده کنید یا اصلا چطوری میشه Style پیشفرض براش تعریف کرد. یه مثال هم میزنه چطوری یه اپی که قبلا نوشته شده رو میشه Theme و Styleهاشو به متریال تغییر داد.</description>
                <category>موبایل لَبْ</category>
                <author>عباس اویسی</author>
                <pubDate>Sun, 29 Nov 2020 00:21:22 +0330</pubDate>
            </item>
                    <item>
                <title>کارهای لازم موقع برنامه نویسی موبایل</title>
                <link>https://virgool.io/MobileLab/%DA%A9%D8%A7%D8%B1%D9%87%D8%A7%DB%8C-%D9%84%D8%A7%D8%B2%D9%85-%D9%85%D9%88%D9%82%D8%B9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D9%85%D9%88%D8%A8%D8%A7%DB%8C%D9%84-auzxfqj8ugih</link>
                <description>دوباره سلامتوی این مقاله یکسری مواردی که تجربی بهشون رسیدم رو میخوام براتون بگم که بعضی هاشون برای راحتی خودتونه و بعضی هاشون برای راحتی کاربرتون. یعنی از لحاظ UX باعث میشه که کاربرتون حس بهتری داشته باشه ( البته من تخصص تو UX ندارم و این موارد تجربی هستن )خیلی فرقی نمیکنه که واسه چه پلتفرمی کد میزنید اما بهتره که این موارد رو رعایت کنید. هرچند که منظورم بیشتر سمت اپ های موبایلی هستش. اینم بگم که ترتیب خاصی نداره...۱- تصاویربهتره همه ی تصاویرتون Placeholderداشته باشن تا وقتی که عکس داره لود میشه کاربر خسته نشه. معمولا اینجور مواقع از لوگوی خود سایت یا اپ استفاده میکنن.اگرهم بتونید Shimmer پیاده سازی کنید فوق العادست.۲- لودینگتا اونجایی که میتونید از لودینگ های به شکل دیالوگ استفاده نکنید. سعی کنی مثل گوگل از Swipe استفاده کنید که یه چیزی اون بالا بچرخه و یا از لودینگ های خطی زیر تولبار استفاده کنید. استفاده از shimmer تو اولویته۳- لیست های خالیاگر یه صفحه دارید که قراره یه لیست از یه سری چیزا مثلا محصولات فروشگاه نشون میده ( مثلا لیست گوشی های سامسونگ ) و اون لیست ممکنه خالی باشه صفحه رو خالی ول نکنید به امید خدا. یه عکس با مفهموم اینکه چیزی پیدا نشد به کاربر نشون بدین بزارین حس خوب بهش دست بده. یا اگر میتونید این عکس رو فقط با ارتفاعی تو حدود ۲۰۰ تا ۳۰۰ dp  نشون بدین و زیرش محصولات دیگه رو معرفی کنید که ممکنه کاربر دوست داشته باشه.۴- مشکل اعداد فارسیخیلی از کاربرا زبون گوشیشون فارسیه و موقعی که شما میخواید یکسری محاسبات روی اعداد انجام بدین یا اون هارو با APIارسال کنید غالبا به مشکل برخورد میکنید. بهتره که این مشکل رو از قبل با نوشتن مثلا یه متد یا به صورت پایه ای حل کنید.۵- استفاده از Shared Preferenceتوی اندروید اطلاعات حساس رو تو  Shared Preference قرار ندید چون عین آب خوردن میشه بهش دسترسی داشته باش. بهتره حداقل از Hawk استفاده کنید یا از روش های جدیدی که خود گوگل پیشنهاد داده کمک بگیرید۶- ویرگولویرگول فارسی با انگلیسی متفاوته. اگر جایی خواستید جایگذاری کنید مثل توی سه رقم سه رقم جدا کردن قیمت حتما حواستون باشه replaceکنید.۷- اسکرول ویو تو همه صفحاتخیلی وقتا کاربرا با گوشی های کوچیک از اپ استفاده میکنن و ممکنه یکسری چیزا رو از دست بدن. دیدم که یه سری اپ ها وقتی کیبوردشون باز میشه دیگه دکمه رو از دست میدی. مجبوری اول کیبورد رو ببندی بعد رو دکمه کلیک کنی. یه اسکرول ویو بزارین این مشکل برطرف میشه.۸- ورژنینگهمیشه ورژن های مختلف اپ که ساین و منتشر میکنید رو بایگانی کنید. به موقعش خیلی مهم میشه ( اگه از گیت استفاده نمیکنید)۹- انیمیشناز انیمیشن استفاده کنید. Lottie بهترین پیشنهاده. حس خوبی به کاربر و خودتون میده.البته منظور این نیست که شورشو درارین اما جاهای مختلفی از برنامه هست که میتونید با انیمیشن حس خوبی به کاربر بدین</description>
                <category>موبایل لَبْ</category>
                <author>مهرداد</author>
                <pubDate>Tue, 10 Nov 2020 14:41:22 +0330</pubDate>
            </item>
                    <item>
                <title>انواع Interceptor ها در OkHttpClient</title>
                <link>https://virgool.io/MobileLab/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-interceptor-%D9%87%D8%A7-%D8%AF%D8%B1-okhttpclient-e8u12hpngu8e</link>
                <description>با سلام خدمت دوستان.یک هفته پیش طی یک مصاحبه کاری که داشتم(البته هنوز هم جایی مشغول نشدم! ) مصاحبه کننده یک سوالی پرسید که جوابی که دادم فکر کردم درست هست ولی ته ذهنم باقی موند تا خودم دقیقتر سرچ بزنم درموردش که نتیجه اش این مطلبی هست که مطالعه می کنید.سوال این بود:  چند نوع OkHttp Interceptors داریم؟با اینکه زیاد باهاشون کار کرده بودم ولی نمیدونستم که چند نوع هستن دقیقا که الان خدمتتون عرض میکنم:  اولا یه تعریف ساده از خودش داشته باشیم تا بعد به جواب سوال بپردازیمتعریف:مکانیسمی برای monitor, rewrite, and retry برای صدا زدن API   ها هست.حالا جواب سوالمون تو تصویر بالا هست: نوع اول Application Interceptorsاین Interceptor  ها بین کد شما و کتابخونه okhttp core  هست و با addInterceptor()  به سازنده OkHttpClientاضافه میشه.نوع دوم Network Interceptorsاین Interceptor  ها هم بین کتابخونه okhttp core و سرور قرار میگیره و داده ها رو رصد میکنه.برای ایجاد یک interceptor  به این طریق عمل میکنیم:class MyInterceptor : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {/*** Our API Call will be intercepted here*/}}حالا به طور مثال به دو مورد از استفاده از Interceptor هارو خدمتتون میگم - میتونید برای مدیریت کدهای ریسپانس ها (401 ، 402، 403، ...) استفاده کنید مثل کد موجود در لینک زیرhttps://gist.github.com/SaeedBahari/cab424176a59df693bd1e050e8703d8a- برای افزودن Header به همه درخواست هاتون استفاده کنید مثل لینک زیر:https://gist.github.com/SaeedBahari/cfc55eaf9ebea681e658d177a0690ad9حالا هر کدوم از کلاس های بالا رو که ساختین به سازنده OkHttpClient  میتونید به روش زیر استفاده کنید:private val apiClient = OkHttpClient().newBuilder().addInterceptor(HeaderInterceptor())  .build()برای بالابردن راندمان و سرعت در رکوئست ها میتونید از قابلیت کش کردن رکوئست ها استفاده کنید. معمولا توسط بک اند کار برای اینکه چند بار رکوئستی زده شد، هربار کوئری زده نشود انجام میشودکه با header زیر درخواست می شود: Cache-Control ولی اگه این قابلیت سمت سرور نیز فعال نشده باشد نیز میتوانیم سمت کلاینت این مورد رو مدیریت کنیمبرای اینکار کلاس زیر رو ایجاد میکنیم: https://gist.github.com/SaeedBahari/0fe4da9e53aa9c8ebb9e598d1e16de2bبعد به سازنده OkHttpClient اضافه میکنیم:private val apiClient = OkHttpClient().newBuilder().addNetworkInterceptor(CacheInterceptor())  .build()این Interceptor  از نوع شبکه می باشد چون در لایه شبکه قرار میگیره.میتونید از مقاله زیر هم که منبعی برای توضیحات من بود استفاده کنید که چند تا روش دیگر استفاده از این مکانیسم رو مثال زده.https://blog.mindorks.com/okhttp-interceptor-making-the-most-of-it</description>
                <category>موبایل لَبْ</category>
                <author>Saeed Bahari</author>
                <pubDate>Fri, 06 Nov 2020 17:20:31 +0330</pubDate>
            </item>
                    <item>
                <title>&quot;اتصال به اینترنت برقرار نیست! تلاش مجدد&quot; یک استراتژی کلی</title>
                <link>https://virgool.io/MobileLab/%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A8%D9%87-%D8%A7%DB%8C%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A8%D8%B1%D9%82%D8%B1%D8%A7%D8%B1-%D9%86%DB%8C%D8%B3%D8%AA-%D8%AA%D9%84%D8%A7%D8%B4-%D9%85%D8%AC%D8%AF%D8%AF-%DB%8C%DA%A9-%D8%B1%D8%A7%D9%87-%D8%AD%D9%84-%DA%A9%D9%84%DB%8C-gqze3pc7zcnc</link>
                <description>اینترنت متصل نیست! تلاش مجدددر این مقاله قصد دارم یک راه حل کلی به جهت  پیاده سازی روشی برای ارسال مجدد ریکوئست های fail شده بخاطر عدم اتصال به اینترنت ارائه بدممن قصد دارم اینجا از RxSwift و Moya استفاده کنم (چرا که کار رو خیلی راحت تر کردن) تا ایده اصلی رو به شما منتقل کنم و بعد شما میتونید به هر نحوی که دوست داشتین پیاده سازیش کنید. ماجرا چیه؟ماجرا از این قراره که ما توی خیلی از برنامه هایی که می نویسیم ریکوئست هایی رو به سرور ارسال کنیم و ممکنه این ریکوئست ها بخاطر عدم اتصال به اینترنت fail بشن.در این جور مواقع باید پیغامی رو با متن و ظاهری مناسب به کاربر نمایش بدیم و مشکل رو بهش یادآوری کنیم و در ادامه هم امکان تلاش مجدد و یا نادیده گرفتن این پیغام رو بهش بدیم.اما چطوری باید همه ریکوئست هایی که توی اون لحظه ارسال شدن و صرفا بخاطر عدم اتصال به اینترنت fail شدن رو دوباره ارسال کنیم؟ اینجاست که  RxSwift و  operator های خفنش وارد میشندر زیر بهتره یک نگاه کلی نسبت به کاری که قراره انجام بدیم داشته باشیم:لایه presentation و network یک رفرنس از  آبجکتی از جنس  GenericNetworkErrorRetryStrategy رو دارن. در واقع این دو لایه به واسطه یک آبجکت مشترک از کلاسی که GenericNetworkErrorRetryStrategy رو پیاده سازی کرده باهم تعامل دارنبا کمک moya یک لایه ساده برای api هامون ایجاد کردیم که خیلی سادست و نیاز به توضیحی نداره:  https://gist.github.com/matinouf/73240c8846fe60821d979cb68f3850f7 استراتژی مااینجا یک protocol به اسم GenericNetworkErrorRetryStrategy داریم که در ادامه پیاده سازیش می‌کنیم اما فعلا فقط قراره بدونیم کاربرد پراپرتی ها و متد هاش چیه: https://gist.github.com/matinouf/e16fa6c7948cccb6d0e31f7bcaedbf9a ۱) این Observable یک مقدار از جنس boolean رو emit میکنه و قراره در لایه network ازش استفاده بشه. یک مثال ساده: زمانی که کاربر روی دکمه تلاش مجدد (در لایه presentation) کلیک می‌کنه این observable مقدار true رو emit میکنه و در زمانی که دکمه cancel کلیک میشه مقدار false رو.۲) از این Observable در لایه presentation استفاده میشه و زمانی که یک ریکوئست fail میشه یکی از case های تعریف شده در Api رو emit میکنه. تا بدونیم به طور دقیق کدوم یکی از ریکوئست ها ناموفق بوده(توی این مقاله از این مورد استفاده نمی‌کنیم).۳) همونطور که از اسمش مشخصه وضعیت نمایش پیامی مبنی بر &quot;عدم اتصال به اینترنت&quot; رو emit میکنه و در لایه presentation قراره بهش subscribe انجام بشه.۴) این متد در لایه Network مورد استفاده قرار میگیره و زمانی که ریکوئستی fail  بشه و دلیلش عدم اتصال به اینترنت باشه بلافاصله صدا زده میشه.۵ و ۶) این دو متد در لایه presentation مورد استفاده قرار میگیرن و زمانی که کاربر روی یکی از دکمه های retry یا cancel کلیک کنه صدا زده میشنخب حالا که با این protocol آشنا شدیم بریم به ساده ترین نوع ممکن پیاده سازیش کنیم: https://gist.github.com/matinouf/7ea3f1c31f3988b7fd8e0ee8db566afe به کمک کلاس PublishRelay در فریم وورک RxCocoa پراپرتی ها رو مقدار دهی کردیم.بعد از اینکه متد ها رو مرور کردید میریم تا ببینیم لایه های network و presentation چطوری قراره از این کلاس استفاده کنن تا بتونن باهم در تعامل باشن.لایه Presentationما یک کلاسی فرضی به نام Application رو در نظر گرفتیم (این میتونه AppDelegate برنامه شما باشه) که در لایه presentation قرار داره و قراره که پیغام عدم اتصال به اینترنت رو به کاربر نمایش بده و همچنین اکشن های retry و  cancel رو به واسطه رفرنسی که از آبجکتی از جنس GenericNetworkErrorRetryStrategy داره به لایه Network منتقل کنه. https://gist.github.com/matinouf/b19fb148f991d83346804004d6458373 توجه کنید که ما تنها یک instance از کلاس GenericNetworkErrorRetryStrategyImpl ایجاد می‌کنیم و در لایه های presentation و network اینجکت میکنیم.لایه Networkدر زیر یک پیاده سازی ساده از پروتکلی که بالاتر تحت عنوان Networking معرفی کردیم رو میبینیم و خواهیم دید این کلاس چطوری از GenericNetworkErrorRetryStrategy استفاده خواهد کرد: https://gist.github.com/matinouf/9513a2e7a4a6d326463d9624c757f5a2 همچنین میتونید همین پیاده سازی رو در یک Plugin/Interceptor (که سر راه ریکوئست ها قرار میگیره) انجام بدید.۱) در اینجا بررسی میکنیم تا اگر error از نوع عدم اتصال به اینترنت بود درخواست نمایش پیغام مناسب رو با صدا زدن متد makeNetworkErrorVisible ایجاد کنیم تا در لایه presentation نمایش این پیغام انجام شود.۲) با استفاده از یکی از operator های مهم در RxSwift تحت عنوان retryWhen خواهیم توانست اکشنی را که کاربر در لایه presentation انجام میدهد عملیاتی کنیم و در صورتی که درخواست  تلاش مجدد ارسال شد  اصطلاحا resubscribe انجام شود تا ریکوئست fail شده مجددا ارسال شود.همچنین میتونید در کلاس GenericNetworkErrorRetryStrategyImpl وضعیت اتصال به اینترنت رو همواره بررسی کنید و هر موقع اتصال برقرار شد متد retry رو صدا کنید تا اگر ریکوئستی fail شده به طور خودکار مجددا ارسال بشه. نتیجه گیریبجای اینکه توی قسمت های مختلف در لایه presentation درگیر هندل کردن مشکلات این چنینی و نوشتن کد های تکراری باشیم میتونیم با پیاده سازی همچین روش هایی هندل کردن این مشکل رو به یک کلاس مجزا بسپاریم تا viewModel ها یا Controller ها درگیری با این مسئله نداشته باشن و روی تسک های خودشون تمرکز کنن.امیدوارم تونسته باشم چیزی که باید رو به شما هم منتقل کنم. اگر جایی رو متوجه نشدید و واستون مبهم بود حتما کامنت کنید. شایم من بد نوشته باشم یا منظورم رو به بهترین شکل منتقل نکرده باشم.در نهایت این الگو رو میتونید کامل تر و شخصی سازی تر کنید و ارور های دیگه ای مثل 401,403, 500 ,.... رو هندل کنید.</description>
                <category>موبایل لَبْ</category>
                <author>متین عبداللهی</author>
                <pubDate>Thu, 29 Oct 2020 14:52:54 +0330</pubDate>
            </item>
                    <item>
                <title>خدافظی با SharedPreferences - آموزش DataStore در اندروید</title>
                <link>https://virgool.io/MobileLab/%D8%AE%D8%AF%D8%A7%D9%81%D8%B8%DB%8C-%D8%A8%D8%A7-sharedpreferences-%D8%A2%D9%85%D9%88%D8%B2%D8%B4-datastore-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-b9vapqoekfm7</link>
                <description>DataStore در اندرویددیتا استور چیه؟دیتا استور(DataStore) کامپوننت جدید Jetpack که قرار جایگزین SharedPreferences بشه، طبیعتا نو که بیاد به بازار یه سری ویژگی های جدید داره، بهترین ویژگی که دیتا استور نسبت به شردپرفرنسز داره اینه که خوندن و نوشتن دیتا به صورت Async و با Flow که کامپوننتی از Kotlin Coroutines هست انجام میشه. چندتا نکته که قبل از ادامه دادن باید بدونید اینه که:دیتا استور در این زمان که این پست نوشته میشه تو حالت آلفا قرار داره که برای استفاده در پروداکشن مناسب نیست.قراره ما با کاتلین کد بزنیمما از Kotlin Coroutines استفاده میکنیم(خیلی سطح پایین)همچنین از Kotlin Extension هم استفاده شدهبرای UI برنامه از Jetpack Compose استفاده شدهچطور ازش استفاده کنیممرحله اول - کتابخونه رو به پروژه اضافه میکنیم:implementation &amp;quotandroidx.datastore:datastore-preferences:1.0.0-alpha02&amp;quotمرحله دوم - ساخت یک نمونه از دیتا استور:private val dataStore by lazy { context.createDataStore(name = &amp;quotdata_store&amp;quot) }مرحله سوم - خوندن و نوشتن:دیتا استور هم مثل شردپرفرنسز داده ها رو به صورت key-value سازماندهی میکنه، اما موضوع برای دیتا استور یکم فرق داره اونم اینه که مثل شردپرفرنسز key ها به صورت string نیستن بلکه اونا باید از نوع Preferences.Key باشن(جلوتر مسئله روشن‌تر میشه) و موضوع دیگه ای هم که هست اگه بخوایم دیتایی رو توی دیتا استور ذخیره کنیم حتما باید از کوروتین کاتلین استفاده کینم، که در ساده ترین حالت فرآیند ذخیره کردن به شکل زیر هست:GlobalScope.launch {
    val TEXT_KEY = preferencesKey&lt;String&gt;(&amp;quottext&amp;quot)
    dataStore.edit {
        it[TEXT_KEY] = &amp;quotThis is just a simple text&amp;quot
    }
}درست مثل شردپرفرنسز تو خط دوم یک کلید برای ذخیره کردن دیتامون ساختیم به اسم TEXT_KEY و خط های بعدی دیتا رو توی دیتا استور ذخیره میکنهوقتی بخوایم داده هایی رو که قبلا ذخیره شدن رو از دیتا استور بخونیم، ما اون دیتا رو در قالب Flow دریافت میکنیم مثل کد زیر:val text: Flow&lt;String&gt; = dataStore.data.map { data -&gt;
    data[key] ?: &amp;quotDefault value&amp;quot // if the data is null we&#039;ll get the &amp;quotDefault value&amp;quot text
}

text.collect { data -&gt;
    println(&amp;quotdata is: $data&amp;quot)
}درسته یکم عجیب غریب به نظر میاد برای همین همونطوری که بالاتر قید شد برای اینکه کار با دیتا استور راحت‌تر و خوانایی کدها بالاتر بره یه سری Extension Function به سورس‌کدمون اضافه کردیم@InternalCoroutinesApi
fun &lt;T&gt; DataStore&lt;Preferences&gt;.liveData(key: Preferences.Key&lt;T&gt;, defaultValue: T): LiveData&lt;T&gt; =
    data.map {
        it[key] ?: defaultValue // if the data is null we&#039;ll get the defaultValue
    }.asLiveData(IO)

fun &lt;T&gt; DataStore&lt;Preferences&gt;.saveData(data: Pair&lt;Preferences.Key&lt;T&gt;, T&gt;) = GlobalScope.launch {
    edit {
        it[data.first] = data.second
    }
}فانکشن اول دیتای ذخیره شده تو دیتا استور رو به صورت LiveData به ما میده که نهایتا نحوه خوندن از دیتا استور رو آسونتر میکنه:val textLiveData: LiveData&lt;String&gt; = dataStore.liveData(TEXT_KEY, &amp;quotnot specified yet&amp;quot) // &amp;quotnot specified yet&amp;quot is the default valueهمینطور نحوه استفاده از فانکشن دوم:dataStore.saveData(TEXT_KEY to &amp;quotThis text is gonna be saved in data store&amp;quot)در نهایت کد کامل اکتیویتی:class MainActivity : AppCompatActivity() {

    private val dataStore by lazy { createDataStore(name = &amp;quotdata_store&amp;quot) }

    @InternalCoroutinesApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val textLiveData: LiveData&lt;String&gt; = dataStore.liveData(TEXT_KEY, &amp;quotnull&amp;quot)

        setContent {
            AppScreen(
                textLiveData = textLiveData,
                onSaveClicked = { saveText(it) }
            )
        }
    }

    private fun saveText(value: String) = dataStore.saveData(TEXT_KEY to value)

    companion object {
        private val TEXT_KEY = preferencesKey&lt;String&gt;(&amp;quottext&amp;quot)
    }میتونید کد کامل رو از گیت دریافت کنید</description>
                <category>موبایل لَبْ</category>
                <author>محمد جهانگیری</author>
                <pubDate>Wed, 28 Oct 2020 10:35:19 +0330</pubDate>
            </item>
                    <item>
                <title>خداحافظ فایربیس : ردیابی کرش ها و مانیتورینگ برنامه با سرویس قدرتمند Yandex - AppMetrica</title>
                <link>https://virgool.io/MobileLab/yandex-appmetrica-firebase-alternative-rdkgftisi5d0</link>
                <description>Yandex - AppMetricaدر این مقاله میخواهیم در مورد جایگزین قدرتمند فایربیس (Firebase) صحبت کنیم که برای مانیتورینگ , گزارش گیری و مهم تر از همه ردیابی کرش های احتمالی در برنامه های اندروید و iOS هست که بعد از انتشار در مارکت های آنلاین کمک های زیادی به ما خواهند کرد.همینطور شاید در جریان تحریم های هوآوی توسط گوگل و دولت آمریکا باشید که باعث شده گوشی های جدید این شرکت و برنامه های ما با مشکلات جدیدی روبرو شه و نتونه کد ها و Dependency های GSM یا همون گوگل پلی سرویسز رو اجرا کنه و کلی دیالوگ های آزار دهنده نشون بده که میگه : “application won’t run without google play services which are not supported by your device”خب ما با استفاده از سرویس قدرتمند Yandex که AppMetrica نام داره میتونیم تمام کدهای GSM رو به این پلتفرم تغییر بدیم و از شر این ارور های آزار دهنده رها بشیم و همینطور از استفاده از این سرویس که هم پیاده سازیش راحته و هم فیلتر نیست و پر امکانات تر هست , لذت ببریم .اپ متریکا یک سرویس قدرتمندی که به ما در مانیتورینگ , گزارش گیری , ارسال ایونت های مختلف , ذخیره اطلاعات کاربران , پوش نوتیفیکیشن و از همه مهم تر ردیابی کرش های رخ داده در برنامه مون کمک میکنه که به جد میتونم بگم خطاها رو دقیق تر و بهتر از فایربیس لیست میکنه .ثبت نامبا استفاده از این لینک میتونید به راحتی یک حساب کاربری در Yandex بسازید و از تمام سرویس های اون به طور کاملا رایگان استفاده کنید.تنظیمات اولیه فایل گریدل برنامه رو باز کنید و کد های زیر رو به اون اضافه کنید :apply plugin: ‘appmetrica-plugin’

android {
    appmetrica { 
       postApiKey = “YOUR_POST_API_KEY” 
       mappingBuildTypes = [‘release’]
    }
}

dependencies {
    implementation ‘com.yandex.android:mobmetricalib:3.15.0’
}بعد از اضافه کردن کد های بالا , فایل گریدل پروژه رو باز کنید و خط زیر رو به اون اضافه کنید:classpath ‘com.yandex.android:appmetrica-build-plugin:0.1.3’پروژه رو Sync کنید و الان آماده استفاده از AppMetrica تو برنامه مون هستیم.تنظیمات اولیه AppMetricaخب بریم برای ستاپ کردن اپ متریکا و با توجه به نیاز هامون میتونیم مشخص کنیم از کدوم فیچر ها استفاده کنیم و کدوم غیرفعال باشند :val config = YandexMetricaConfig.newConfigBuilder(API_KEY)
        .withNativeCrashReporting(false)
        .withLocationTracking(false)
        .withAppVersion(BuildConfig.VERSION_NAME)
        .build() 

YandexMetrica.activate(this, config)
YandexMetrica.enableActivityAutoTracking(this)اول از همه نیازه که یک Api Key بسازید که با استفاده از این لینک میتونید پروژه تون رو اضافه کنید و Api Key و Post Api Key رو از داشبورد بردارید.اگر پروژه شما شامل کدهای نیتیو (C++) هست و میخواهید که کرش های نیتیو هم شناسایی کنید فقط کافیه مقدار withNativeCrashReporting رو true کنید و همینطور میخواهید موقعیت مکانی کاربران تون رو داشته باشید هم مقدار withLocationTracking رو تغییر بدید .همینطور با مشخص کردن ورژن برنامه تون میتونید خطا ها و کل اطلاعات رو دسته بندی کنید و در داشبورد اپ متریکا ببینید.ذخیره اطلاعات کاربرانپنل لسیت کاربرانشما میتونید اطلاعات کاربران برنامه تون رو با استفاده از AppMetrica ذخیره کنید و اونها رو با فیلتر های مختلقی  مثل کشور , شهر , نوع دستگاه و.. روی داشبورد مشاهده کنید.val userProfile = UserProfile.newBuilder()
        .apply(Attribute.name().withValue(name))
        .apply(Attribute.customString(“Email”).withValue(email))
        .build()

YandexMetrica.setUserProfileID(id)
YandexMetrica.reportUserProfile&#40;userProfile&#41;اپ متریکا مقادیری مثل نام , جنسیت و تاریخ تولد رو از قبل ساخته و با استفاده از اونها میتونید اطلاعات کاربران رو ذخیره کنید . همنیطور امکان ذخیره اطلاعات اضافی مثل ایمیل , عکس و.. رو بهتون میده که با استفاده از Attribute.customString/Boolean/Number شما میتونید هر نوع اطلاعاتی رو ذخیره کنید.ارسال ایونت ایونت ها در داشبورداپ مریکا این امکان رو به شما میده که ایونت های مختلفی رو ارسال و ذخیره کنید . برای مثال با باز شدن هر صفحه از برنامه یک ایونت بفرستیم که معادل setCurrentScreent در فایربیس میتونه باشه و همینطور میتونید عملکرد های مختلف مثل کلیک رو با ارسال ایونت و ذخیره آنها شناسایی کنید.YandexMetrica.reportEvent(string)ذخیره درآمدپنل درآمد در داشبوردبا استفاده از اپ متریکا شما میتونید درآمد های حاصل از اپلیکیشن تون رو ذخیره و گزارش گیری کنید و بعنوان یک حسابدار از این سرویس استفاده کنید . شما میتونید هر محصول رو با اسم و آیدی مشخص و تعداد و.. در Yandex ذخیره کنید تا اطلاعات کاملی از فروش و سود برنامه تون داشته باشید.val revenue = Revenue.newBuilderWithMicros(1000, Currency.getInstance(“IRR”))
        .withProductID(“PD-123”)
        .withQuantity(1)
        .build()

YandexMetrica.reportRevenue(revenue)گزارش دستی خطا لیست کرش های برنامه اپ متریکا در کنار ثبت خودکار خطا ها , به شما این امکان رو میده که دستی خطاهایی رو به سمت پنل بفرستید و در داشبورد بررسی شون کنید :try {
     …
} catch (ex: Exception) {
    YandexMetrica.reportUnhandledException(ex)
}نتیجه اپ متریکا سرویس Yandex یک پلتفرم کاملا رایگان و قدرتمند که به شما برای گزارش گیری و مانیتورینگ برنامه تون کمک های زیادی میکنه و به جرات میتونم بگم تجربه استفاده بهتری نسبت به فایربیس با این سرویس داشتم که از لحاظ گزارش خطاها واقعا بهتر و دقیق تر هست و همینطور فیلتر نیست و با خیال راحت میتونیم از این سرویس استفاده کنیم.منبع : https://appmetrica.yandex.com/docs/quick-start/concepts/quick-start.htmlسورس کد نمونه : https://gist.github.com/alirezanazari/cecec7f8d4231d04ad9a121137e98b16</description>
                <category>موبایل لَبْ</category>
                <author>علیرضا نظری</author>
                <pubDate>Sun, 25 Oct 2020 15:39:32 +0330</pubDate>
            </item>
                    <item>
                <title>گریدل پروژتو تمیز کن</title>
                <link>https://virgool.io/MobileLab/%DA%AF%D8%B1%DB%8C%D8%AF%D9%84-%D9%BE%D8%B1%D9%88%DA%98%D8%AA%D9%88-%D8%AA%D9%85%DB%8C%D8%B2-%DA%A9%D9%86-uwzwrbyofpb0</link>
                <description>چند وقتیه که کثیف بودن گریدلم خیلی رو اعصاب بوده و گریدل اکثر پروژه هام در بهترین حالت ممکن این شکلی بود:Beforeو حالا با استفاده از Kotlin DSL میشه یکم مرتب ترش کرد:نکته: دو اسکریپت بالا واسه دو پروژه مختلف هستن و ربطی به هم ندارن! اما پروسه تغییر به کاتلین DSL رو این پایین می‌نویسم.برای اطلاعات بیشتر ارائه سنا عبادی رو در لاگ کت ببینید: https://www.youtube.com/playlist?list=PLT2xIm2X7W7jRcn9QvxaUA1JoZ3jrHv7W مرحله اول، ساخت پوشه buildSrc: در root پروژه خودتون، پوشه buildSource رو به صورت زیر و با این ترتیب بنویسید:بعد ساخت پوشه ابتدا پوشه سورس رو به ترتیب: src -&gt; main -&gt; javaایجاد کنید و فایل Dependencies.kt رو داخلش بسازید. بعد از این مرحله، گریدل اسکریپت خودتون رو داخل پوشه buildSrc ایجاد کنید:build.gradle.kts:import org.gradle.kotlin.dsl.`kotlin-dsl`
plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
}حالا پروژه رو sync کنید و منتظر بمانید!بعد از sync شدن پروژه، دو پوشه gradle و build به پوشه buildSrc اضافه میشه که اگه از ورژن کنترل(git) استفاده می‌کنید، بهتره که اونارو به gitignore اضافه کنید. به صورت دستی فایل گیت ایگنور رو اضافه کنید و به صورت زیر عمل کنید:.gradle
/buildحالا نوبت اینه که پروژه اصلیمون رو به کاتلین DSL منتقل کنیم!مرحله دوم، تغییر Settings به kts: settings.gradle:include &#039;:app&#039;
rootProject.name = &amp;quotStationery&amp;quotsettings.gradle.kts:include(&amp;quot:app&amp;quot)
rootProject.name = &amp;quotStationery&amp;quotپروژه رو Sync کنید.مرحله سوم، تغییر فایل های build:ابتدا فایل Dependencies رو به اینصورت بنویسید:object Dependencies {

    object Versions {
    }

    object DefaultConfig {
    }

    object Plugins {
    }

    object Libraries {
        object Test {
        }
    }

    object ClassPaths {
    }
}آبجکت Versions برای نگه داری نسخه ها، آبجکت DefaultConfig برای نگه داری کانفیگ گریدل app، آبجکت Plugins برای نگه داری پلاگین ها، آبجکت Libraries برای نگه داری کتابخونه ها و آبجکت Test برای نگه داری کتابخونه های تست، آبجکت ClassPaths برای نگه داری ClassPath های ما ?اول ClassPath ها و ورژنای اونارو اضافه می کنیم:object Versions {
    const val gradle = &amp;quot4.1.0&amp;quot
    const val kotlin = &amp;quot1.4.10&amp;quot
}object ClassPaths {
    const val gradle = &amp;quotcom.android.tools.build:gradle:${Versions.gradle}&amp;quot
    const val kotlin = &amp;quotgradle-plugin&amp;quot
}بیلد گردیل ماژول پروژه رو ویرایش می‌کنیم:build.gradle:// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = &amp;quot1.3.72&amp;quot
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath &amp;quotcom.android.tools.build:gradle:4.1.0&amp;quot
        classpath &amp;quotorg.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version&amp;quot

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}build.gradle.kts:// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url = uri(&amp;quothttps://jitpack.io&amp;quot) }
    }
    dependencies {
        classpath(Dependencies.ClassPaths.gradle)
        classpath(kotlin(Dependencies.ClassPaths.kotlin, version = Dependencies.Versions.kotlin))

        classpath(Dependencies.ClassPaths.hilt)
        classpath(Dependencies.ClassPaths.safeArgs)
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url = uri(&amp;quothttps://jitpack.io&amp;quot) }
    }
}

tasks.register(&amp;quotclean&amp;quot,Delete::class){
    delete(rootProject.buildDir)
}حالا میرسیم به تغییر گریدل ماژول اپ، اول اطلاعاتی که لازم داریم رو داخل Dependencies که ساختیم اضافه میکنیم:object Dependencies {

    object Versions {
        const val gradle = &amp;quot4.1.0&amp;quot
        const val kotlin = &amp;quot1.4.10&amp;quot
        const val coreKTX = &amp;quot1.3.1&amp;quot
        const val appCompat = &amp;quot1.2.0&amp;quot
        const val constraintLayout = &amp;quot2.0.1&amp;quot
        const val legacySupport = &amp;quot1.0.0&amp;quot
        const val espresso = &amp;quot3.3.0&amp;quot
        const val jUnit = &amp;quot4.13&amp;quot
        const val arch = &amp;quot2.1.0&amp;quot
        const val concurrent = &amp;quot1.1.0&amp;quot
        const val lifeCycle = &amp;quot2.2.0&amp;quot
        const val hilt = &amp;quot2.28-alpha&amp;quot
        const val hiltAndroid = &amp;quot1.0.0-alpha02&amp;quot
        const val leakCanary = &amp;quot2.4&amp;quot
    }

    const val compileSdkVersion = 30
    const val buildToolsVersion = &amp;quot30.0.2&amp;quot

    object DefaultConfig {
        const val applicationID = &amp;quotir.moeindeveloper.stationery&amp;quot
        const val minSdKVersion = 19
        const val targetSdkVersion = 30
        const val versionCode = 1
        const val versionName = &amp;quot1.0&amp;quot
        const val testInstrumentationRunner = &amp;quotandroidx.test.runner.AndroidJUnitRunner&amp;quot
    }

    object Plugins {
        const val application = &amp;quotcom.android.application&amp;quot
        const val android = &amp;quotandroid&amp;quot
        const val kotlinExtensions = &amp;quotandroid.extensions&amp;quot
        const val kapt = &amp;quotkapt&amp;quot
        const val hilt = &amp;quotdagger.hilt.android.plugin&amp;quot
        const val safeArgs = &amp;quotandroidx.navigation.safeargs.kotlin&amp;quot
    }

    object Libraries {
        const val kotlin = &amp;quotorg.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}&amp;quot
        const val coreKTX = &amp;quotandroidx.core:core-ktx:${Versions.coreKTX}&amp;quot
        const val appCompat = &amp;quotandroidx.appcompat:appcompat:${Versions.appCompat}&amp;quot
        const val constraintLayout = &amp;quotandroidx.constraintlayout:constraintlayout:${Versions.constraintLayout}&amp;quot
        const val legacySupport = &amp;quotandroidx.legacy:legacy-support-v4:${Versions.legacySupport}&amp;quot
        const val concurrent = &amp;quotandroidx.concurrent:concurrent-futures-ktx:${Versions.concurrent}&amp;quot
        const val viewModel = &amp;quotandroidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifeCycle}&amp;quot
        const val liveData = &amp;quotandroidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifeCycle}&amp;quot
        const val lifeCycleCommon = &amp;quotandroidx.lifecycle:lifecycle-common-java8:${Versions.lifeCycle}&amp;quot
        const val activity = &amp;quotandroidx.activity:activity-ktx:${Versions.activity}&amp;quot
        const val hilt = &amp;quotcom.google.dagger:hilt-android:${Versions.hilt}&amp;quot
        const val hiltCompiler = &amp;quotcom.google.dagger:hilt-android-compiler:${Versions.hilt}&amp;quot
        const val hiltAndroid = &amp;quotandroidx.hilt:hilt-lifecycle-viewmodel:${Versions.hiltAndroid}&amp;quot
        const val leakCanary = &amp;quotcom.squareup.leakcanary:leakcanary-android:${Versions.leakCanary}&amp;quot

        object Test {
            const val espressoContrib = &amp;quotandroidx.test.espresso:espresso-contrib:${Versions.espresso}&amp;quot
            const val espressoIntents = &amp;quotandroidx.test.espresso:espresso-intents:${Versions.espresso}&amp;quot
            const val espressoAccessibility = &amp;quotandroidx.test.espresso:espresso-accessibility:${Versions.espresso}&amp;quot
            const val espressoWeb = &amp;quotandroidx.test.espresso:espresso-web:${Versions.espresso}&amp;quot
            const val espressoIdlingConcurrent = &amp;quotandroidx.test.espresso.idling:idling-concurrent:${Versions.espresso}&amp;quot
            const val jUnit = &amp;quotjunit:junit:${Versions.jUnit}&amp;quot
        }
    }

    object ClassPaths {
        const val gradle = &amp;quotcom.android.tools.build:gradle:${Versions.gradle}&amp;quot
        const val kotlin = &amp;quotgradle-plugin&amp;quot
        const val hilt = &amp;quotcom.google.dagger:hilt-android-gradle-plugin:${Versions.hilt}&amp;quot
        const val safeArgs = &amp;quotandroidx.navigation:navigation-safe-args-gradle-plugin:${Versions.navigation}&amp;quot
    }
}خب حالا فایل گریدل ماژول اپ رو به کاتلین DSL تغییر میدیم:build.gradle:plugins {
    id &#039;com.android.application&#039;
    id &#039;kotlin-android&#039;
}

android {
    compileSdkVersion 30
    buildToolsVersion &amp;quot30.0.2&amp;quot

    defaultConfig {
        applicationId &amp;quotir.moeindeveloper.stationery&amp;quot
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName &amp;quot1.0&amp;quot

        testInstrumentationRunner &amp;quotandroidx.test.runner.AndroidJUnitRunner&amp;quot
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile&#40;&#039;proguard-android-optimize.txt&#039;&#41;, &#039;proguard-rules.pro&#039;
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = &#039;1.8&#039;
    }
}

dependencies {

    implementation &amp;quotorg.jetbrains.kotlin:kotlin-stdlib:$kotlin_version&amp;quot
    implementation &#039;androidx.core:core-ktx:1.2.0&#039;
    implementation &#039;androidx.appcompat:appcompat:1.1.0&#039;
    implementation &#039;com.google.android.material:material:1.1.0&#039;
    implementation &#039;androidx.constraintlayout:constraintlayout:1.1.3&#039;
    testImplementation &#039;junit:junit:4.+&#039;
    androidTestImplementation &#039;androidx.test.ext:junit:1.1.1&#039;
    androidTestImplementation &#039;androidx.test.espresso:espresso-core:3.2.0&#039;
}قسمت پلاگین:plugins {
    id(Dependencies.Plugins.application)
    kotlin(Dependencies.Plugins.android)
    kotlin(Dependencies.Plugins.kotlinExtensions)
    kotlin(Dependencies.Plugins.kapt)
    id(Dependencies.Plugins.hilt)
    id(Dependencies.Plugins.safeArgs)
}برای پلاگین هایی که برا کاتلین هستن از kotlin استفاده می کنیم مثل kotlin-android و دیگه نیازی نیست که کاتلین رو اول عبارت بنویسیم و اینطوری کافیه: const val android = &amp;quotandroid&amp;quotقسمت اندروید:android {
    compileSdkVersion(Dependencies.compileSdkVersion)
    buildToolsVersion(Dependencies.buildToolsVersion)

    defaultConfig {
        applicationId = Dependencies.DefaultConfig.applicationID
        minSdkVersion(Dependencies.DefaultConfig.minSdKVersion)
        targetSdkVersion(Dependencies.DefaultConfig.targetSdkVersion)
        versionCode = Dependencies.DefaultConfig.versionCode
        versionName = Dependencies.DefaultConfig.versionName
        multiDexEnabled = true
        testInstrumentationRunner = Dependencies.DefaultConfig.testInstrumentationRunner
    }

    buildTypes {
        getByName(&amp;quotrelease&amp;quot) {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile&#40;&amp;quotproguard-android-optimize.txt&amp;quot&#41;, &amp;quotproguard-rules.pro&amp;quot)
        }
    }

    buildFeatures {
        viewBinding = true
    }


    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }


    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}قسمت وابستگی ها:dependencies {
    implementation(Dependencies.Libraries.kotlin)
    implementation(Dependencies.Libraries.coreKTX)
    implementation(Dependencies.Libraries.appCompat)
    implementation(Dependencies.Libraries.constraintLayout)
    implementation(Dependencies.Libraries.legacySupport)

    /*
    arch components
     */
    implementation(Dependencies.Libraries.concurrent)
    implementation(Dependencies.Libraries.viewModel)
    implementation(Dependencies.Libraries.liveData)
    implementation(Dependencies.Libraries.lifeCycleCommon)
    implementation(Dependencies.Libraries.activity)
    /*
    arch components
     */


    /*
    Hilt
     */
    implementation(Dependencies.Libraries.hilt)
    kapt(Dependencies.Libraries.hiltCompiler)
    implementation(Dependencies.Libraries.hiltAndroid)
    /*
    Hilt
     */
    debugImplementation(Dependencies.Libraries.leakCanary)


    /*
    Test libs
     */
    androidTestImplementation(Dependencies.Libraries.Test.espressoCore)
    androidTestImplementation(Dependencies.Libraries.Test.espressoContrib)
    androidTestImplementation(Dependencies.Libraries.Test.espressoIntents)
    androidTestImplementation(Dependencies.Libraries.Test.espressoAccessibility)
    androidTestImplementation(Dependencies.Libraries.Test.espressoWeb)
    androidTestImplementation(Dependencies.Libraries.Test.espressoIdlingConcurrent)
    testImplementation(Dependencies.Libraries.Test.jUnit)
    /*
    Test libs
     */

}build.gradle.kts:plugins {
    id(Dependencies.Plugins.application)
    kotlin(Dependencies.Plugins.android)
    kotlin(Dependencies.Plugins.kotlinExtensions)
    kotlin(Dependencies.Plugins.kapt)
    id(Dependencies.Plugins.hilt)
    id(Dependencies.Plugins.safeArgs)
}
android {
    compileSdkVersion(Dependencies.compileSdkVersion)
    buildToolsVersion(Dependencies.buildToolsVersion)

    defaultConfig {
        applicationId = Dependencies.DefaultConfig.applicationID
        minSdkVersion(Dependencies.DefaultConfig.minSdKVersion)
        targetSdkVersion(Dependencies.DefaultConfig.targetSdkVersion)
        versionCode = Dependencies.DefaultConfig.versionCode
        versionName = Dependencies.DefaultConfig.versionName
        multiDexEnabled = true
        testInstrumentationRunner = Dependencies.DefaultConfig.testInstrumentationRunner
    }

    buildTypes {
        getByName(&amp;quotrelease&amp;quot) {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile&#40;&amp;quotproguard-android-optimize.txt&amp;quot&#41;, &amp;quotproguard-rules.pro&amp;quot)
        }
    }

    buildFeatures {
        viewBinding = true
    }


    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }


    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}
dependencies {
    implementation(Dependencies.Libraries.kotlin)
    implementation(Dependencies.Libraries.coreKTX)
    implementation(Dependencies.Libraries.appCompat)
    implementation(Dependencies.Libraries.constraintLayout)
    implementation(Dependencies.Libraries.legacySupport)

    /*
    arch components
     */
    implementation(Dependencies.Libraries.concurrent)
    implementation(Dependencies.Libraries.viewModel)
    implementation(Dependencies.Libraries.liveData)
    implementation(Dependencies.Libraries.lifeCycleCommon)
    implementation(Dependencies.Libraries.activity)
    /*
    arch components
     */


    /*
    Hilt
     */
    implementation(Dependencies.Libraries.hilt)
    kapt(Dependencies.Libraries.hiltCompiler)
    implementation(Dependencies.Libraries.hiltAndroid)
    /*
    Hilt
     */
    debugImplementation(Dependencies.Libraries.leakCanary)


    /*
    Test libs
     */
    androidTestImplementation(Dependencies.Libraries.Test.espressoCore)
    androidTestImplementation(Dependencies.Libraries.Test.espressoContrib)
    androidTestImplementation(Dependencies.Libraries.Test.espressoIntents)
    androidTestImplementation(Dependencies.Libraries.Test.espressoAccessibility)
    androidTestImplementation(Dependencies.Libraries.Test.espressoWeb)
    androidTestImplementation(Dependencies.Libraries.Test.espressoIdlingConcurrent)
    testImplementation(Dependencies.Libraries.Test.jUnit)
    /*
    Test libs
     */
}گریدل رو سینک کنید! کار شما تموم شد و گردیل شما مرتبه و هندل کردن اطلاعات آسون تر شده و به راحتی میتونید نسخه هارو تغییر بدید!نکته مهم: چینش کانفیگ شما به این صورت کاری زمان بر و وقت گیره! برای راحتی کار از این پلاگین استفاده کنید: https://github.com/jmfayard/refreshVersions اگه پیشنهادی، انتقادی یا سوالی داشتید در کامنت ها واسم بنویسید!</description>
                <category>موبایل لَبْ</category>
                <author>محمد معین عبدی</author>
                <pubDate>Tue, 20 Oct 2020 15:43:42 +0330</pubDate>
            </item>
                    <item>
                <title>کار کردن با API ‌وبسایت Stack OverFlow !</title>
                <link>https://virgool.io/MobileLab/%DA%A9%D8%A7%D8%B1-%DA%A9%D8%B1%D8%AF%D9%86-%D8%A8%D8%A7-api-%D9%88%D8%A8%D8%B3%D8%A7%DB%8C%D8%AA-stackoverflow-o05fl7rwd1kw</link>
                <description>کار کردن با API ‌وبسایت StackOverFlow !من علاقه خاصی به کار با API های مختلف دارم. به عنوان مثال ،مدتی پیش با API گیت هاب کار کردم و در یک مقاله راجع به آن توضیحاتی دادم .این بار من با API وبسایت Stack OverFlow  کار کردم و تجربه خیلی خیلی خوبی بود. در اینجا خلاصه ایی از محتویات ریپازیتوری گیت هابم قرار خواهم داد ولی برای بررسی کامل لطفا به مرجع مراجعه کنید .لینک ریپازیتوری در گیت هاب زبان برنامه نویسی استفاده شده :Kotlinمعماری استفاده شده :Clean Architectureکامپوننت های استفاده شده :RXRetrofitOKhttp3ViewModelLiveDataNavigation ComponentRecyclerViewMaterial Designمرجع API :Stack Exchange API v2.2بیس url :https://api.stackexchange.com/تصاویر :stack over flowامیدوارم که با Fork ‌کردن بتوانید کاملترش کنید و  بررسی و مطالعه کد برای شما مفید واقع شود .سنا عبادی |  پنج شنبه شب ساعت 23:06 , دهم مهر سال 1399</description>
                <category>موبایل لَبْ</category>
                <author>سنا عبادی</author>
                <pubDate>Thu, 01 Oct 2020 23:07:50 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مبحث شیرین Serialization در جاوا</title>
                <link>https://virgool.io/MobileLab/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D8%A8%D8%AD%D8%AB-%D8%B4%DB%8C%D8%B1%DB%8C%D9%86-serialization-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7-vkfz2ilcnvye</link>
                <description>فرض کنید که شما در برنامه خود یک آبجکت از یک کلاس دارید که یک سری داده هم در اون آبجکت ذخیره کردید و حالا می خواهید این داده ها رو دریک فایل ذخیره کنید چه می کنید؟زبان جاوا یک مکانیزم جذاب به نام object serialization در اختیار ما قرار می‌دهد، در این مکانیزم می‌تونیم آبجکت رو به شکل دنباله‌ای از بایت‌ها در جریان خروجی بنویسیم و بعدا برش گردونیم. دنباله بایت ها شامل داده‌های آبجکت، نوع آبجکت چیه و نوع داده‌های ذخیره شده در آبجکت چی هست، می‌باشد.پس از اینکه آبجکت سریال‌سازی شده در یک فایل نوشته شد، می توانیم از فایل اونو بخونیم و deserialize اش کنیم یعنی با استفاده از اطلاعاتی که در بایت‌ها ذخیره شده مثل نوع داده و داده های درون آبجکت، آن آبجکت را در حافظه بازسازی کنیم.(خیلی ساده و آسون!)جذاب‌ترین بخش این فرآیند آن است که به ماشین مجازی جاوا (JVM) وابسته نیست به طوری‌که می‌تونه در یک پلتفرم serialized شود و سپس در پلتفرم کاملاً متفاوت دیگری deserialized گردد.حالا می‌خواهم دو تا کلاس مهم رو بهتون معرفی کنم که با استفاده از اون‌ها بتونید خیلی راحت و ساده سریالایز و دی سریالایز رو انجام بدید! کلاس‌های ObjectInputStream و ObjectOutputStream جریان‌های سطح بالایی هستند که در خودشون توابعی برای سریالایز و دی سریالایز کردن آبجکت ها دارند.کلاس ObjectOutputStream توابع نوشتن زیادی برای نوشتن نوع داده‌های مختلف دارد. اما تابع زیر از همه برجسته‌تر است: public final void writeObject(Object x) throws IOException تابعی که در بالا می‌بینید یک آبجکت را در ورودی می‌گیرد و اون آبجکت رو سریالایز کرده و به جریان خروجی (output stream) ارسال‌اش می‌کند. خُب پس به طور مشابه کلاس  ObjectInputStream هم تابع زیر را برای دی سریالایز کردن آبجکت دارد.public final Object readObject() throws IOException, ClassNotFoundExceptionاین تابع آبجکت رُ از جریان ورودی (فایلی که قبلا آبجکت توش ذخیره شده) بازیابی و دی سریالایز می‌کند. خروجی این تابع یک آبجکت است که شما باید آن را به کلاس موردنظرتان cast کنید.حالا این که سریالایز چطور در جاوا کار می کند را از طریق یک مثال با کمک از کلاس Employee نشان می‌دهیم. کلاس Employee زیر را داریم که اینترفیس Serializable را پیاده‌سازی کرده است:public class Employee implements java.io.Serializable {
   public String name;
   public String address;
   public transient int SSN;
   public int number;
   
   public void mailCheck() {
      System.out.println(&amp;quotMailing a check to &amp;quot + name + &amp;quot &amp;quot + address);
   }
}توجه کنید برای آن که یک کلاس با موفقیت سریال سازی شود باید دو تا شرط مهم را رعایت کند:شرط اول: کلاس باید اینترفیس java.io.Serializable را پیاده‌سازی کرده باشد.شرط دوم: همه ی فیلدهای کلاس باید قابل سریال‌سازی باشند. اگر فیلدی قابل سریال‌سازی نباشد باید توسط transient نشانه گذاری گردد.اگر براتون جالب بود که یک کلاس استاندارد در جاوا قابل سریال سازی هست یا خیر، داکیومنت آن را مطالعه کنید. تست آن هم بسیار ساده است، اگر کلاس java.io.Serializable  را پیاده‌سازی کند قابل سریال سازی است و اگرنه نیست.معرفی دی سریالایز (Deserialize)کردن یک آبجکت:همون طور که گفتیم کلاس ObjectOutputStream برای سریالایز کردن آبجکت استفاده می‌شه. مثالی می‌زنیم از برنامه SerializeDemo که یک آبجکت از Employee می‌سازد و آن را درون یک فایل سریال‌سازی می‌کند. وقتی اجرای برنامه تمام می‌شود یک فایل employee.ser ایجاد شده است. توجه: وقتی که یک آبجکت را به یک فایل سریالایز می‌کنید، استاندارد جاوا این است که به فایل پسوند .ser بدهد.import java.io.*;
public class SerializeDemo {

   public static void main(String [] args) {
      Employee e = new Employee();
      e.name = &amp;quotReyan Ali&amp;quot
      e.address = &amp;quotPhokka Kuan, Ambehta Peer&amp;quot
      e.SSN = 11122333;
      e.number = 101;
      
      try {
         FileOutputStream fileOut =
         new FileOutputStream(&amp;quot/tmp/employee.ser&amp;quot);
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(e);
         out.close();
         fileOut.close();
         System.out.printf(&amp;quotSerialized data is saved in /tmp/employee.ser&amp;quot);
      } catch (IOException i) {
         i.printStackTrace();
      }
   }
}خُب حالا یک FileOutputStream رو برای نوشتن بایت های خروجی باز می کنیم و یک مسیر ذخیره فایل هم در سازنده بهش می‌دهیم.سپس یک شی از کلاس دوست داشتنی ObjectOutputStream می‌گیریم و جریان داده خروجی رو بهش می‌دهیم. حالا وقتی چیه؟ بله متد نوشتن آبجکت در فایل رو فراخوانی کنیم. (متد writeObject )حالا اگر یادتون باشه، کار این متد، گرفتن آبجکت (نمونه از کلاس Employee) و سریالایز کردن اون و ارسالش به جریان خروجی هست. بعدم که تابع کلوز شی های ساخته شده رو فراخوانی می کنیم که بندگان خدا سرگردون نباشن. (منابع آزاد بشن)از سریال خارج کردن (Deserializing) آبجکت:  برنامه زیر آبجکت Employee که در برنامه قبلی سریال سازی شده بود را deserialize می‌کند. import java.io.*;
public class DeserializeDemo {

   public static void main(String [] args) {
      Employee e = null;
      try {
         FileInputStream fileIn = new FileInputStream(&amp;quot/tmp/employee.ser&amp;quot);
         ObjectInputStream in = new ObjectInputStream(fileIn);
         e = (Employee) in.readObject();
         in.close();
         fileIn.close();
      } catch (IOException i) {
         i.printStackTrace();
         return;
      } catch (ClassNotFoundException c) {
         System.out.println(&amp;quotEmployee class not found&amp;quot);
         c.printStackTrace();
         return;
      }
      
      System.out.println(&amp;quotDeserialized Employee...&amp;quot);
      System.out.println(&amp;quotName: &amp;quot + e.name);
      System.out.println(&amp;quotAddress: &amp;quot + e.address);
      System.out.println(&amp;quotSSN: &amp;quot + e.SSN);
      System.out.println(&amp;quotNumber: &amp;quot + e.number);
   }
}خروجی برنامه بالا به صورت زیر است:Deserialized Employee...
Name: Reyan Ali
Address:Phokka Kuan, Ambehta Peer
SSN: 0
Number:101نکات زیر قابل توجه است:بلاک try/catch تلاش می‌کند ClassNotFoundException که در تابع (readObject) اعلان شده است را catch کند. برای آنکه ماشین مجازی جاوا (JVM) بتواند یک آبجکت را deserialize کند باید بایت کد مربوط به کلاس را پیدا کند، و اگر نتواند بایت کد را پیدا کند این اکسپشن را ایجاد می‌کند.توجه کنید که خروجی تابع readObject به رفرنسی به  Employee ، فرم دهی (cast) خواهد شد.مقدار ابتدایی فیلد SSN برابر با 11122333 بود، ولی اگر یادتان باشد ما آن را با transient نشانه گذاری کردیم بهمین دلیل در زمان سریال سازی به جریان خروجی ارسال نشد و مقدار آن در آبجکت deserialize شده برابر صفر شد. این نوشته ترجمه‌ی آزادی از اینجا بود و اگر می‌خواهید در این مطلب عمیق شوید. همین فصل از کتاب Core Java جلد دوم را در اینجا بخوانید. </description>
                <category>موبایل لَبْ</category>
                <author>فرشته ناجی</author>
                <pubDate>Thu, 24 Sep 2020 22:48:15 +0330</pubDate>
            </item>
                    <item>
                <title>آموزش استفاده از Jetpack DataStore</title>
                <link>https://virgool.io/MobileLab/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-jetpack-datastore-o05hittehxcl</link>
                <description>سلام دوستانمن مدت‌ها قصد داشتم توی ویرگول خودم نوشتن رو شروع کنم اما به دلایل مختلفی هیچوقت تا الان نشد که این اتفاق بیفته! تا اینکه چند روز پیش خبر منتشر شدن کتابخونه‌ی جدید DataStore که بخشی از مجموعه‌ی Jetpack هست رو خوندم.از اونجایی که این کتابخونه مزایای بسیار زیادی نسبت به SharedPreferences داره، و آموزش فارسی برای پیاده‌سازیش هنوز منتشر نشده(یعنی من ندیدم?)، تصمیم گرفتم این مقاله رو بنویسم. پس خیلی حرف نمی‌زنم و میرم سر اصل مطلب.کتابخونه‌ی DataStore عضو جدیدی از خانواده‌ی Jetpack هست که بر پایه‌ی Kotlin coroutines و Flow ساخته شده و به عنوان جایگزین SharedPreferences معرفی شده.چرا DataStore؟ مگه SharedPreferences چشه؟!خب قبل اینکه بخوام بگم چرا، این جدول مقایسه رو ببینید:SharedPreferences VS DataStoreخب حالا وقت جواب دادنه! معایب SharedPreferences که توی DataStore رفع شدن: اول اینکه SharedPreferences دارای یک API همزمانی (synchronous API) هستش، که به نظر می‌رسه برای صدا زده شدن توی ترد اصلی برنامه (UI Thread) امن باشه، اما در واقع عملیات I/O دیسک رو انجام میده. علاوه بر اون، متد apply ترد اصلی رو روی متد fsync بلاک می‌کنه (برای نوشتن اطلاعات روی حافظه‌ی دستگاه). این یعنی هر موقع یک سرویس یا اکتیویتی در اپلیکیشن شما  start/stop میشه، fsync هایی که pending هستن اجرا میشن و در تمام این مواقع، حین اجرای fsync ها، ترد UI بلاک میشه.به دلیل پشتیبانی نکردن از type safety می‌تونه parsing error یا runtime exception پرتاب کنه و باعث ایجاد مشکل یا کرش در برنامه‌ی ما بشه.از پیگیری کردن وضعیت(success / failure) یک job پشتیبانی نمی‌کنه.فقط از داده‌های اولیه(primitive types) پشتیبانی می‌کنه.نکته مهم: اگه به به‌روزرسانی‌های جزئی، یکپارچگی دسترسی به اطلاعات یا پشتیبانی از مجموعه داده‌های بزرگ یا پیچیده نیاز دارید بهتره (و باید) از Room استفاده کنید. DataStore برای مجموعه داده‌های ساده و کوچک ایده‌آل هست، مثل ذخیره تنظیمات یا اطلاعات کاربری جزئی در نرم‌افزار.روش پیاده‌سازیاینجا فعلا روش پیاده‌سازی بدون Proto رو آموزش میدم که مقاله خیلی طولانی و حوصله سر بر نشه :)۱. اضافه کردن dependency به build.gradle:// Preferences DataStore
implementation &amp;quotandroidx.datastore:datastore-preferences:1.0.0-alpha01&amp;quot۲. ساختن دیتااستور:// with Preferences DataStore
val dataStore: DataStore&lt;Preferences&gt; = context.createDataStore(
    name = &amp;quotsettings&amp;quot //نام دلخواه
)۳. ذخیره‌ی اطلاعات در دیتااستور:dataStore.edit {preferences -&gt;
 preferences[KEY] = value
}به جای KEY باید کلید مربوطه و به جای value مقدار اون رو قرار بدید، مثلا:KEY: &amp;quotlanguage&amp;quot
value: &amp;quotfa&amp;quot۴. خواندن اطلاعات ذخیره شده:val language: Flow&lt;String?&gt;
    get() = dataStore.data.map { preferences -&gt;
        preferences[KEY]
    }بالاتر گفتم که این کتابخونه بر پایه‌ی Kotlin coroutines و Flow ساخته شده، پس باید برای خوندن اطلاعات نوع متغیر رو به صورت بالا تعریف کنیم، و داخل تابع getter دیتای مربوط به KEY دلخواه خودمون رو دریافت کنیم.تبریک می‌گم، شما DataStore رو با موفقیت پیاده‌سازی کردین :)اگر قصد مهاجرت از SharedPreferences به DataStore رو دارید و میخواید دیتای قدیمی رو به دیتااستور منتقل کنید، کافیه کد زیر رو (قبل از انجام هر عملیاتی با DataStore) بنویسید:val dataStore: DataStore&lt;Preferences&gt; = context.createDataStore(
    name = &amp;quotsettings&amp;quot // نام دلخواه,
    migrations = listOf(SharedPreferencesMigration(context, &amp;quotsettings_preferences&amp;quot))
)امیدوارم این مقاله براتون مفید بوده باشه.موفق‌تر باشید ❤منبع: Prefer Storing Data with Jetpack DataStore</description>
                <category>موبایل لَبْ</category>
                <author>میلاد محمدی</author>
                <pubDate>Tue, 22 Sep 2020 11:14:48 +0330</pubDate>
            </item>
                    <item>
                <title>Smooth Corner Transition</title>
                <link>https://virgool.io/MobileLab/smooth-corner-transition-zhj9blksp27i</link>
                <description>یکی از چالش هایی که تو پروژه اخیر داشتم این بود که توی یک صفحه ای ما یک دیزاینی داشتیم که Corner داشت و نیاز بود وقتی اسکرول میشه و به بالا میرسه Corner از بین بره و صاف بشه و tablayout که روز های هفته داخلش ست شده بود اون بالا pin بشه و فیکس بمونه , خب اینکار هم باعث دسترسی بهتر کاربر به روز های هفته میشه و هم تجربه بهتری و حس خوبی از اپ میگیره بنظر من باید با کاربر مثل بچه های کوچیک رفتار کرد , همه چی اماده و جلو دستش باشه و زحمتی نکشه , نباید بهش سخت گرفت و پیچیدش کرد چون بچه ست و زود رنجه البته تا جایی که امکانش هست و واقعا بعضی جاها امکان ساده کردن راه وجود نداره قبل از اینکه وارد دیزاین بشیم یک سری پیش نیاز ها برای دیزاین نیاز داریم !خب ما میخوایم radius رو از مثلا به 25 به 0 تبدیل کنیم من اومدم دوتا shape ساختم که این دو حالت توش تعریف شده باشه یعنی : این shape حالت ساده ما هست که هیچ radius نداره و وقتی کاربر صفحه رو اسکرول میکنه و tablayout ما به بالای صفحه میرسه باید تبدیل به این بشهو برای حالت دیگمون همینه فقط بهش radius اضافه میکنیم میخوام چیکار کنم !؟میخوام با یک حالت انیمیشن این دوتا shape رو transition کنم که این حس القا بشه که داره تبدیل میشه به فلت پس میام یه فایل Drawable میسازم به صورت زیر و از این دوتا shape بالا استفاده میکنمدیزاین اصلی دیزاین اصلی رو بصورت زیر میزنم که یک حالت پارالکس بتونم ایجاد کنمتوجه داشته باشید که به Tablayout باید بکگراند بدیم , حالا کدوم بکگراند ؟ همون tansition که ساختیم با استفاده از اون دوتا shape هابرسیم سر وقت اصل کار !برای به ثمر رسوندن کارمون من کد رو به چند تا قسمت تبدیل کردم از نظر من برای خوانایی کد هاتون هیچوقت بیش از یک کار رو داخل یک تابع انجام ندید و وظیفه هر تابع فقط یک کار باشه اینجوری هم دسترسی اسون تر میشه و هم کداتون تمیز تر میشهبه چند تا تابع نیاز داریم :تابعی برای تغییر رنگ و محتویات Status Bar چون من رنگ سرمه ای به دیزاین بالایی صفحه ام دادم و وقتی اسکرول میشه بگراند پایینی من سفیده پس Status Bar هم باید سفید بشه و اینکه بخوام بصورت ناگهانی و خیلی سریع Status Bar رو تغییر رنگ بدم حس خوبی رو القا نمیکنه و به نظرم اصلا حرفه ای نیست پس اومدم از Value Animator استفاده کردم و تو یه بازه زمانی مشخصی به صورت خیلی نرم تغییر رنگ دادم  :خب حالا اون changeStatusBarUiVisibility داخل تابع بالا چیه ؟ وقتی رنگ status bar رو تغییر میدید اون نوشته ها و یکون هایی که درونش قرار دارن بسته به نوع تم گوشی شما یک رنگی هستن و ممکنه با رنگ که شما دارید به status bar میدید تداخل داشته باشه و دیده نشه پس باید اونا هم بسته به نوع شرایطش تغییر بدید:ولی خب حالا این تابع ها رو کجا استفاده کنیم ؟ اصل اصل کار !وقتی که AppBarLayout ما وضعیتش تغییر کرد باید ما متوجه بشیم که ایا کامل بسته شده یا باز شده ( tab layout ما به صورت کامل چسبیده به بالاترین نقطه صفحه یا نه ؟ )قبل از اینکه listener واسه appbar ایجاد کنیم اول بریم Transition رو درست کنیم با استفاده از فانکشن هایی که بالا ایجاد کردیم یه تابع جدید میسازیم :اینجا چیکار کردم !؟ خب خیلی سادست اومدم اول اون drawable که به Tablayout دادم رو ازش گرفتم و چک کردم که ایا appbar layout بسته ست یا بازه و از طریق اون فهمیدم که باید انیمیشن رو شروع کنم یا بازگردانی و بعدش status bar رو رنگش رو تغییر دادم , از طریق کد listener زیر هم میتونید متوجه بشید که app bar در چه وضعیتی قرار داره :و در نهایت ؟اینم نتیجه کار ما : منتظر کامنت های شما هستم :)میخواستم از gist استفاده کنم ولی انگار ویرگول باگ داره  https://vrgl.ir/4z9l2 Telegram : BehnamNasehii</description>
                <category>موبایل لَبْ</category>
                <author>Behnam Nasehi | بهنام ناصحی</author>
                <pubDate>Tue, 15 Sep 2020 13:19:54 +0430</pubDate>
            </item>
                    <item>
                <title>آموزش کار با Epoxy در اندروید - قسمت ۱</title>
                <link>https://virgool.io/MobileLab/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-%DA%A9%D8%A7%D8%B1-%D8%A8%D8%A7-epoxy-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%D9%82%D8%B3%D9%85%D8%AA-%DB%B1-wc1tb9dgraus</link>
                <description>Epoxyتوی این مقاله قرار بگیم Epoxy چیه و چکارهایی می تونه برای ما انجام بده یا به عبارت دیگه چطور زندگی برامون راحت تر کنه :)تقریبا  برای همون پیش اومده که بخوایم لیست های پیچیده توی اندروید پیاده سازی کنیم خب راه حلی که براش بود اینه که بیایم برای recycler view مون تایپ های متفاوتی تعریف کنیم و لیست ها بر اون اساس پیاده سازی کنیم جدا از چالش هایی که اون حالت داره کلی boilerplate هم داریم حالا با epoxy میشه این کار ساده تر انجام داد در واقع شعارش هم همین پیاده سازی لیست های پیچیده به سادگی هست.این لایبری توسط شرکت Airbnb و برای پیاده سازی صفحات پیچیده با recycler view توسعه داده شده است.بریم که شروع کنیم :دو مفهوم در epoxy خیلی مهم هستند :اول ) Epoxy Model : آیتم های Recycler.ViewHolder کنترل می کند و در واقع کار اصلیش اینه که یک کلاس مدل را برای هر آیتم ایجاد می کند تا دیتا های مربوط به آن ها را نمایش بدهد. ضمنا یک کلاس immutable هست و کاری شبیه به view model انجام میدهد دیتا دریافتی برای نمایش به view میدهد.دوم ) Epoxy Controller : مشخص میکنه که کدام مدل ها باید به recyclerView برای نمایش اضافه شوند و از این طریق یک صفحه پیچیده کاملا ساده پیاده سازی کنیم.به سه روش زیر می توان Epoxy Model را ایجاد کرد : (در این مقاله از روش سوم استفاده می کنیم)۱ - custom view۲- data binding ۳- view holderاول epoxy به پروژه امون اضافه می کنیم :apply plugin: &#039;kotlin-kapt&#039; 

kapt {
    correctErrorTypes = true
}

dependencies {
      implementation  &#039;com.airbnb.android:epoxy:3.6.0&#039; 
     kapt &#039;com.airbnb.android:epoxy-processor:3.6.0&#039;
}بعد از اون آیتم های لیست رو می سازیم :&lt;?xml version=&amp;quot1.0&amp;quot encoding=&amp;quotutf-8&amp;quot?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&amp;quothttp://schemas.android.com/apk/res/android&amp;quot
    android:layout_width=&amp;quotwrap_content&amp;quot
    android:layout_height=&amp;quot56dp&amp;quot
    xmlns:app=&amp;quothttp://schemas.android.com/apk/res-auto&amp;quot&gt;

    &lt;TextView
        android:id=&amp;quot@+id/title&amp;quot
        android:layout_width=&amp;quotwrap_content&amp;quot
        android:layout_height=&amp;quotwrap_content&amp;quot
        android:text=&amp;quotHello World!&amp;quot
        app:layout_constraintBottom_toBottomOf=&amp;quotparent&amp;quot
        app:layout_constraintLeft_toLeftOf=&amp;quotparent&amp;quot
        app:layout_constraintRight_toRightOf=&amp;quotparent&amp;quot
        app:layout_constraintTop_toTopOf=&amp;quotparent&amp;quot /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;و بعد نوبت خود epoxy که به ui اکتیویتی اضافه بشه :&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&amp;quothttp://schemas.android.com/apk/res/android&amp;quot
    xmlns:app=&amp;quothttp://schemas.android.com/apk/res-auto&amp;quot
    xmlns:tools=&amp;quothttp://schemas.android.com/tools&amp;quot
    android:layout_width=&amp;quotmatch_parent&amp;quot
    android:layout_height=&amp;quotmatch_parent&amp;quot
    tools:context=&amp;quot.MainActivity&amp;quot&gt;

   &lt;com.airbnb.epoxy.EpoxyRecyclerView
       android:id=&amp;quot@+id/headerList&amp;quot
       android:layout_width=&amp;quotmatch_parent&amp;quot
       android:layout_height=&amp;quotmatch_parent&amp;quot
       /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
در مرحله بعد باید بریم و Epoxy Model را  درست کنیم که طبق چیزی که قبلا گفتیم ما از روش view holder استفاده می کنیم :@EpoxyModelClass(layout = R.layout.header_view)
abstract class HeaderViewWithViewHolderEpoxy : EpoxyModelWithHolder&lt;Holder&gt;(){

    @EpoxyAttribute lateinit var title : String

    override fun bind(holder: Holder) {
        holder.title.text = title
    }

}

class Holder:KotlinHolder() {
        val title by bind&lt;TextView&gt;(R.id.title)
}همون طور که می بینید توی خط اول و از طریق انوتیشن EpoxyModelClass لایه ای که باید infleted بشه رو بهش معرفی می کنیم. توی متد bind هم دیتا دریافتی از طریق EpoxyAttribute ها به ویو bind میکنه.اگه از java استفاده می کنید که view holder شبیه به همون view holder ریسایکلر ویو هست فقط از EpoxyHolder ارث بری می کند.  اما برای kotlin میشه کلاس KotlinHolder به پروژه اضافه کرد و خیلی راحت از اون استفاده کنیم.حالا که epoxy model ما ساخته شد به یک epoxy controller احتیاج داریم که برای نمایش باید به اون اضافه شود.class HeaderViewController : EpoxyController() {
    
    override fun buildModels() {
      HeaderViewWithKotlinModelEpoxy(&amp;quotfood&amp;quot) 
      .id(1)// به هر آیتم باید یک ایدی نسبت داده شود
     .addTo(this) / /  به این طریق مدل به کنترلر اضافه می کنیم
      HeaderViewWithKotlinModelEpoxy(&amp;quotgame&amp;quot).id(2).addTo(this)
      HeaderViewWithKotlinModelEpoxy(&amp;quotcode&amp;quot).id(3).addTo(this)
    }
}هر کنترلر یک متد buildModels داره که از طریق اون مشخص میشه که چه آیتم هایی باید نمایش داده شوند. نکته خوب epoxy  درواقع همین جاست که توی این متد cotroller هر model که قبلا ساخته باشیم می شه گذاشت و کاملا داینامیک هست.(در این مثال فقط از یک model استفاده شده اما از هر چندmodel که بخواهیم میتونم استفاده کنیم.)یه حالت هم داره برای تعریف دو تایپ که در این لینک می تونید بیشتر بخونید. دیگه فقط قسمت اخرش مونده که اینو به ریسایکلر مون معرفی کنیم تا لیست مورد نظر ما رو نمایش بدهval controller = HeaderViewController()
controller.isDebugLoggingEnabled = true // برای دیباگ کردن و چک کردن لیست بسیار کاربردی هست اما ضروری نیست.

headerList.apply {
    layoutManager = LinearLayoutManager(this@MainActivity,LinearLayoutManager.VERTICAL,false)
    adapter = controller.adapter
    controller.requestModelBuild() // برای زمانی که دیتا تغییر می کنه استفاده میشود.
}
هر کنترلر یک adaper داره که برای ست کردن adapter ریسایکلر ویو می تونیم از اون استفاده کنیم.متد requestModelBuild زمانی که دیتا تغییر کنه دوباره buildModel فراخوانی میکنه تا تغییرات جدید به مدل ها بایند شده و آپدیت شوند.حالا ما از epoxy برای پروژه امون استفاده کردیم.چیزی که epoxy برای من جذاب تر کرد قسمت کاتلینش بود که توی مقاله بعدی با یه کلاس ساده تر و خیلی راحت modelهامون می سازیم و استفاده می کنیم.داکیومنت خود epoxy که لینکش توی قسمت منابع اومده خیلی کامل تر توضیح داده و پیشنهاد میکنم حتما مطالعه کنید.قسمت دوممنابع : https://github.com/airbnb/epoxy/wiki  https://android.jlelse.eu/simplifying-recycler-view-with-epoxy-in-kotlin-nachos-tutorial-series-946d22116d57 </description>
                <category>موبایل لَبْ</category>
                <author>Fatemeh Movassaghpour</author>
                <pubDate>Mon, 14 Sep 2020 18:43:41 +0430</pubDate>
            </item>
                    <item>
                <title>لاگ گیری از ریکوئست های Retrofit</title>
                <link>https://virgool.io/MobileLab/%D9%84%D8%A7%DA%AF-%DA%AF%DB%8C%D8%B1%DB%8C-%D8%A7%D8%B2-%D8%B1%DB%8C%DA%A9%D9%88%D8%A6%D8%B3%D8%AA-%D9%87%D8%A7%DB%8C-retrofit-vt7cfyymnx5z</link>
                <description>گرفتن لاگ به صورت اتوماتیک برای تمامی ریکوئست‌هازمانی‌که حرف ریکوئست‌های سمت سرور پیش می‌آید، همیشه با دیتاهایی به شکل JSON مواجه می‌شویم.لاگ گرفتن دستی از خروجی‌های تمامی ریکوئست‌ها، کار سخت و زمان‌گیری است. این کار موجب‌ می‌شود نه تنها کدها زیاد شوند بلکه نگهداری آن‌ها نیز سخت‌تر می‌شود.اگر از کتابخانه Retrofit برای ارسال و دریافت ریکوئست‌هایتان از سرور استفاده می‌کنید، این کتابخانه یک ویژگی فوق العاده به نام Interceptor‌ها دارد. Interceptor به معنای دخالت‌گر است. این ویژگی زمانی‌که هنوز Interface‌های معروف Retrofit یعنی OnResponse و OnFailure جوابی در اختیار ما قرار نداند، دخالت می‌کند.کاری که این کتابخانه انجام می‌دهد، لاگ گرفتن از خروجی ریکوئست هر چیزی و با هر فرمتی است. این بدین معنی است که نیازی نیست شما دستی، تک تک ریکوئست‌ها را لاگ گیری کنید.مراحل پیاده سازی را یک به یک جلو میرویم.اضافه کردن کتابخانه به پروژهمی توانید از این صفحه نسخه آخر این کتابخانه را پیدا کنید.implementation(&amp;quotcom.squareup.okhttp3:logging-interceptor:4.8.1&amp;quot)ایجاد و تخصیص سطح دسترسیHttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();اگر می‌خواهید لاگ‌ها فقط زمانی‌ نمایش داده شوند که اپلیکیشن در مرحله دیباگ (نه ریلیز) است، از این تکه کد استفاده کنید. (پیشنهاد من: تنظیمات زیر را حتما اعمال کنید)if (BuildConfig.DEBUG) {
    interceptor.level(HttpLoggingInterceptor.Level.BODY);
} else {
    interceptor.level(HttpLoggingInterceptor.Level.NONE);
}اضافه کردن Interceptor به OkHttpClient.BuilderOkHttpClient.Builder httpClientBuilder = new OkHttpClient().newBuilder().addInterceptor(interceptor);تماااام.شما به همین راحتی می‌توانید این کار را انجام دهید. زمانی‌که ریکوئستی ارسال یا دریافت می‌شود، در Logcat عبارت okhttp را بنویسید تا لاگ ریکوئست‌های شما نمایش داده شود.به عنوان مثاللاگ کت در حالت verbose قرار داردپی نوشتدر صفحه خود این کتابخانه به این موضوع اشاره شده است که چون Header و بعضی از دیتاهای حیاتی و مهم را هم لاگ گیری می‌کند، مواظب نوع استفاده از این کتابخانه باشید. این کتابخانه برای این مشکل هم یک راه‌حل ارائه داده است. شما می‌توانید کلمه کلیدی خود را برای این‌که در لاگ‌ها نمایش داده نشود، با استفاده از تکه کد زیر اضافه کنید.بدین صورت:logging.redactHeader(&amp;quotAuthorization&amp;quot);
logging.redactHeader(&amp;quotCookie&amp;quot);می‌توانید کد کامل کلاس را اینجا پیدا کنید.به نظر شما چه کدها و روش‌های جایگزینی برای این موضوع وجود دارد؟ شما روش بهتری برای این کار سراغ دارید؟</description>
                <category>موبایل لَبْ</category>
                <author>علیرضا نظامی</author>
                <pubDate>Mon, 31 Aug 2020 19:08:04 +0430</pubDate>
            </item>
                    <item>
                <title>هفت کتاب مهمی که هر برنامه نویس باید بخواند !</title>
                <link>https://virgool.io/MobileLab/%D9%87%D9%81%D8%AA-%DA%A9%D8%AA%D8%A7%D8%A8-%D9%85%D9%87%D9%85%DB%8C-%DA%A9%D9%87-%D9%87%D8%B1-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3-%D8%A8%D8%A7%DB%8C%D8%AF-%D8%A8%D8%AE%D9%88%D8%A7%D9%86%D8%AF-igvutcnzq7jl</link>
                <description>هفت کتاب مهمی که هر برنامه نویسی باید بخواند !در این پست قرار هست  چند کتاب خیلی خوب و فوق العاده ایی که هر برنامه نویسی در طول زندگی حرفه ای اش باید بخواند را معرفی کنم .  این کتاب ها به طور خاص به یک زبان یا تکنولوژی مشخصی مرتبط نیستند و این می تواند یکی از مهمترین دلایلی باشد که من آنها را انتخاب کردم . حتی اگر یک کتاب از یک زبان برنامه نویسی برای شرح اطلاعات استفاده کند ، زبان برنامه نویسی یک هدف نیست ، بلکه وسیله ای برای روشن شدن است و اصول ارائه شده در کتابها برای هر کسی که می خواهد سطح فنی خود را بهبود بخشد ،بسیار مفید است .کتاب اول : Code Complete 2nd Edition کتاب اول ، کتابی است که در کتابهای مهندسی نرم افزار امتیاز خاصی را دارا می باشد که نویسنده ی این کتاب  یکی از بهترین نویسندگان و متخصصان این حوزه می باشد! نویسنده ی این کتاب  Steve McConnell می باشد .این کتاب مجموعه ای از اصول مهم را برای هر برنامه نویس مانند مقابله با پیچیدگی و چگونگی حل آن ،  مفاهیمی همچون ماژولارسازی ، انتزاعی بودن را ارائه می دهد. مراحل مختلف موجود در آن ، با تمرکز بر مراحل ساخت ، که الزامات ، طراحی و  کد نویسی هستند را نشان می دهد.این کتاب به شما می آموزد که مهندسی نرم افزار به معنای واقعی چیست و چگونه می توانید مهندس نرم افزار بمانید و یکی از بهترین فصل های کتاب فصل 33 است که در مورد شخصیت شخصی و چگونگی ساخت شخصیت و اصول خود به عنوان مهندس نرم افزار صحبت می کند.Code Complete 2nd Edition کتاب دوم : Clean Codeکتاب دوم  کتابی است که درباره آنچه که یک کد تمیز است صحبت  می کند ، همانطور که از نام کتاب پیداست در رابطه با کد نویسی تمیز و کاربردی است . پیچیدگی را کاهش می دهد و در صورت لزوم آن را ساده می کند .نویسنده ی این کتاب Robert C.Martin  است که معروف به عمو باب می باشد.این کتاب بخشی از مجموعه ای ازسری کتاب های Clean است که Clean Coder و Architecture Clean است نیز در این دسته قرار می گیرند .آنها را نیز در لیست خواندنی های مهم قرار دهید.این کتاب کاربردی را کاملا خوانده ام و بسیار کاربردی و مفید می باشد .Clean Codeکتاب سوم : Working Effectively with legacy Codeکتاب سوم کتابیست که در مورد مشکلاتی است که هر برنامه نویس در طول زندگی حرفه اییش با آن ها مواجه می شود  یا به قولی با Legacy Code درگیر خواهد شد و البته شروع کتاب با یک تعریف از Legacy Code چیست و چرا اتفاق می افتد و چرا همه برنامه نویسان از آن رنج می برند ، و بیشتر برنامه نویسان در آن شرایط   چه اشتباهاتی می کنند , خواهد بود.نویسنده ی این کتاب Michael C.Feathers  می باشد.Working Effectively with legacy Codeکتاب چهارم :  Soft Skills: The Software Developers Life Manualکتاب چهارم که باز هم در مورد برنامه نویسی نیست اما هر برنامه نویس باید این کتاب را بخواند.کتاب عالی در مورد مدیریت &quot;سایر&quot; جنبه های زندگی توسعه دهندگان. این مربوط به همه چیزهایی است که می تواند در زندگی شما وجود داشته باشد - این مربوط به شغل ، زندگی ، بدن ، ذهن شما است ، اگر باور می کنید یا نه - روح شما نیز تحت تاثیر قرار خواهد گرفت .خود نویسنده از این تکنیک ها پیروی کرده است و در این امر کاملاً موفق است.در وبلاگش می گوید که  در اوایل دهه 30 توانسته است کار روزانه خود را ترک کند. نویسنده تجارب زندگی خود را در فصل های كوتاهی  در زمینه شغلی ، بازاریابی خود ، یادگیری ، بهره وری ، امور مالی ، تناسب اندام و روح به اوج  رسیده خود سخن می گوید . هر فصل به اندازه کافی کوتاه است، که باعث می شود خواندن آن نسبتاً سبک باشد.نویسنده ی این کتاب John Z.Sonmez می باشد .Soft Skillsکتاب پنجم : Refactoring: Improving the Design of Existing Codeمهم نیست که چقدر سخت تلاش می کنید ، کدی که ارائه می دهید بهینه نخواهد بود مگر اینکه در مورد آن توسعه ها و پیشرفت هایی صورت گرفته باشد.این کتاب با اصول کلی Refactoring آغاز می شود,  چرا و چه موقع Refactor کنیم و  چگونگی دستیابی به مدیریت در مورد تغییر کاربری و ... را در بر دارد . این کتاب سپس شما را در مورد چگونگی improvements آشنا می کند.شاخص بوی بد در کد چیست؟چگونه می توان استراکچر کلاسها ،و متد ها  را ساخت؟تست واحد یا unitTest برای کد شماچگونه فیچر ها را بین آبجکت های مختلف جا به جا کنیم؟ابزارهای Refactoringو ...در مورد چگونگی بهبود کد موجود ، باید در مورد هر موضوعی که در بالا اشاره شد کتاب بخوانید. توجه داشته باشید که تمام نمونه های  کدهای موجود به زبان جاوا می باشند ، اما نباید مانع از یادگیری شما شود! Refactoring: Improving the Design of Existing Codeکتاب ششم : Head First Design Patternsغیر فنی ترین کتاب در زمینه ی برنامه نویسی! هر صفحه شامل  تصاویر و سایر موارد چشم نواز است. این ممکن است این تصور را ایجاد کند که خواندن آن ساده و راحت است ، اما  واقعیت این است که درمورد یک موضوع کاملاً اساسی در مورد برنامه نویسی - الگوهای طراحی - بحث می کند وبسیار عالی موضوع را پوشش می دهد.این کتاب سعی نمی کند تمام الگوهای موجود در جهان را پوشش دهد ، بلکه شامل هر الگوی لازم برای حل مشکلات دنیای واقعی می شود. این به شما کمک می کند تا نرم افزاری کاربردی ، زیبا ، قابل استفاده مجدد و انعطاف پذیر ایجاد کنید. معاملات هر الگو به روشنی بیان شده است. بیشتر کتابهای مربوط به الگوی طراحی در مورد چگونگی اجرای این الگوی صحبت می کنند ، اما نویسندگان این کتاب همچنین دلیل و چگونگی آن را توضیح می دهند.آخرین نسخه کتاب اکنون شامل به روزرسانی های جاوا 8 - عمدتاً لامبدا - است.Head First Design Patternsکتاب هفتم : Writing Secure Code 2nd Editionکتاب آخری که قرار هست معرفی کنم  Writing Secure Code 2nd Edition است که همچنین یکی از مهمترین کتاب هایی است که هر برنامه نویسی باید بخواند ، زیرا این امر دید شما را به اهمیت تفکر در مورد امنیت در تمام مراحل ساخت سیستم باز می کند و این کتاب به ویژه کتابی پیشگام در این زمینه بود . داستان از آنجایی آغاز شد که مایکروسافت با مشکلات امنیتی را که در دهه نود میلادی در محصولات خود داشت رو به رو شد و بخشی از مشکل این بود که روند ساخت یک نرم افزار ایمن توسعه نیافته است و این انگیزه اصلی بیل گیتس بود که  Memo را بر اساس Trustworthy Computing  معروف به رایانه قابل اعتماد در سال 2002 به وجود آورد . که بر این اساس تیمی با همین نام در این شرکت ایجاد شده و هدف آن تحقیق و توسعه در زمینه امنیت نرم افزار است و این کتاب یکی از نتایج تحقیقات آنها بود.این کتاب نیازهای مهم بسیاری برای هر برنامه نویس را برای تفکر و نوشتن کد ایمن جمع آوری کرده است. مهمترین نیازهایی که هر تیم یا شرکتی می تواند برای تجزیه و تحلیل سیستم در طی مراحل طراحی از آن استفاده کند و از آن برای یافتن مشکلات امنیتی و نظارت بر کاهش یا راه های جلوگیری از آنها استفاده کند.Writing Secure Code 2nd Editionاین لیست بر اساس دانسته ها و نظرات من گرد آوری شده است . پس شما میتوانید عناوین کتاب ها را تغییر بدهید و برای خود لیست جدیدی تهیه کنید و آن را با ما به اشتراک بگذارید .از توجه شما عزیزان سپاسگذارم . به امید پیشرفت و توسعه ی تک تک شما بزرگواران سنا عبادی |  عصر روز پنج شنبه ساعت 19:35 , شانزدهم مرداد سال 1399 </description>
                <category>موبایل لَبْ</category>
                <author>سنا عبادی</author>
                <pubDate>Thu, 06 Aug 2020 19:40:48 +0430</pubDate>
            </item>
                    <item>
                <title>تسلط بر TouchEvent ها در اندورید</title>
                <link>https://virgool.io/MobileLab/%D8%AA%D8%B3%D9%84%D8%B7-%D8%A8%D8%B1-touchevent-%D9%87%D8%A7-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D9%88%D8%B1%DB%8C%D8%AF-mvyaciolpqun</link>
                <description>در این مقاله سعی کردم بصورت مفصل درباره TouchEvent ها در اندروید توضیح بدم و نکات لازم رو بنویسم. سوالی که همیشه هنگام کار با کاستوم ویو ها پیش میاد اینه که از onTouchEvent استفاده کنیم یا onInterceptTouchEvent ؟ اصلا چه فرقی دارن و چیکار میکنن؟ ?چرخه Touch: یک نمونه Hierarchy از لایه ها در اندروید وقتی که کاربر ویوی از صفحه نمایش رو لمس میکنه متد onInterceptTouch طبق سناریوی زیر صدا زده میشه. کار این متد مطلع کردن لایه های پایین تر از اکشن های تاچ هست. متد onInterceptTouch رو فقط ViewGroup ها دارن. میتوان برای مطلع شدن از Touch در اکتیویتی  از متد dispatchTouchEvent و برای مطلع شدن از Touch در view ها از onTouchEvent استفاده کرد. هر سه متد مقدار بازگشتیشان از نوع Boolean هست و اگر به متد true برگردد یعنی Touch هندل شده و به لایه بعدی منتقل نکن، ولی اگر false برگردد یعنی Touch هندل نشده و event به لایه بعدی منتقل بشه.سناریو و ترتیب صدا شدن متد ها: با لمس کردن صفحه ابتدا اکتیویتی این نوتیفای رو دریافت میکنه و متد dispatchTouchEvent صدا زده میشه. در صورت false بودن event به Window فرستاده میشه. window یک abstract class  هست که پیاده سازی این کلاس PhoneWindow هست. بعد dispatchTouchEvent برای DecorView صدا زده میشه که این کلاس از یک FrameLayout  ارث برده شده، در این کلاس استاتوس بار و باتوم نویگیشن هندل میشن. این کلاس چون یک ViewGroup هست پس متد onInterceptTouchEvent هم داره که صدا زده میشه. ما چون دسترسی ای به این دو کلاس نداریم پس کاری هم باهاشون نداریم و فقط اینجا آوردم تا سلسله مراتب کامل گفته بشه. مرحله بعد نوبت به ViewGroup هامون میرسه. ViewGroup اول rootView هستش. همون ViewGroup که تو لایه xml برای اکتیویتی در نظر میگیریم. ViewGroup دوم هم child اولی هست. همونطوری که بالا گفته شد ترتیب صدا شدن متد ها تو ViewGroup ها اول dispatchTouchEvent هست. بعد onInterceptTouchEvent. در آخر درصورت false بودن مقدار بازگشتی  همه این ها که تا اینجا اومدی متد onTouchEvent در View صدا زده میشه. اگه به view اینترفیس OnTouchListener داده شده باشه متد OnTouchListener.onTouch و onTouchEvent هر دو صدا زده میشن، اگر null بود فقط onTouchEvent صدا زده میشه.ازینجا به بعد چی میشه؟ پس onTouchEvent های لایه های بالاتر چی؟ چرا اونا صدا زده نشدن؟؟؟ اول کمی بهش فکر کنید بعد به تصویر پایین نگاه کنید.چرخه Touch در اندرویددر تصویر بالا کلاس های PhoneWindow و DecorView رو چون بهشون دسترسی نداریم نیاوردم. مسیر گیج کننده ای که بالا بهتون توضیح دادم رومیتونید تو عکس بالا بررسی کنید و با مسیر حرکت کنید تا براتون شفاف بشه. dispatchTouchEvent و onInterceptTouchEvent از بالا ترین لایه میان تا پایین ترین لایه. به همه لایه ها نوتیفای میکنن که Touch رخ داده و آبجکتی از نوع MotionEvent  با خودشون حمل می کنند. متد onTouchEvent از پایین ترین لایه شروع به حرکت می کنه و اگر کسی true برنگردونه تا بالاترین لایه یعنی اکتیویتی میره. این رو بصورت یک چرخه دائم و حرکت در نظر بگیرید تا بتونه اولین متدی که true برگردونه رو پیدا کنه. این لووپ اینقدر تکرار میشه تا onTouchEvent ای رو پیدا کنه که true شده. فرض کنید از لایه بالا سروع به حرکت میکنه تا به ViewGroup B برسه و در اینجا متد onInterceptTouchEvent مقدار true بر میگردونه. این true برگردوندن یعنی من تاچ رو هندل میکنم و به لایه های پایینتر نده. بعد onTouchEvent از ViewGroup B صدا زده میشه. اگه true باشه هنگام انتشار اکشن های بعدی این چرخه دیگه تکرار نمیشه و مستقیم فقط متد onTouchEvent از ViewGroup B  صدا زده میشه. اگه هم false برگردونه که چرخه باز تکرار میشه ولی لایه های پایین تر دیگه نمیرسه.تا اینجای کار چرخه Touch رو سعی کردم کامل توضیح بدم. و الان وقتشه تا با اکشن ها آشنا شیم.انواع اکشن ها:آبجکت MotionEvent به ما چهار اکشن مهم میده. اکشن های دیگه ای هم هست که اینجا گفته نمیشن.ACTION_DOWNACTION_UPACTION_MOVEACTION_CANCELسری اول که صفحه رو لمس می کنیم ACTION_DOWN داده میشه. یعنی کاربر انگشتش رو گذاشت رو صفحه. سری های بعدی ACTION_MOVE که یعنی کاربر داره انگشتش رو روی صفحه میکشه و در آخر ACTION_UP که یعنی کاربر انگشتش رو از روی صفحه برداشت ?. حالا ACTION_CANCEL پس چیه؟ این اکشن زمانی داده میشه که onInterceptTouchEvent میاد Touch رو هندل میکنه و به لایه پایین تر خودش ACTION_CANCEL  رو میده.متد requestDisallowTouchInterecept کارش چیه؟ ?فرض کنید یک کاستوم ویو دارید که داخل یک اسکرول ویو هست. و میخوایید داخل این کاستوم ویو یک مربع رو جابجا کنید. در حالت عادی اگه اینکار رو کنید اسکرول ویو هم جابجا میشه ?. اینجا تو متد onTouchEvent کاستوم ویو باید متد requestDisallowTouchInterecept رو صدا بزنیم تا به لایه های بالاش بگه که دیگه onInterceptTouchEvent صدا زده نشه? اینجوری مشکل حل میشه و دیگه اسکرول نمیشه. فقط مربع تکون میخوره.اینم از  چرخه Touch در اندروید ? اگه جاییش مشکل داشت لطفا راهنمایی کنید تا اصلاحش کنیم.</description>
                <category>موبایل لَبْ</category>
                <author>hamed.rahimvand</author>
                <pubDate>Mon, 03 Aug 2020 15:25:44 +0430</pubDate>
            </item>
                    <item>
                <title>استفاده از Dagger hilt به صورت پروژه محور (قسمت سوم)</title>
                <link>https://virgool.io/MobileLab/%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-dagger-hilt-%D8%A8%D9%87-%D8%B5%D9%88%D8%B1%D8%AA-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D9%85%D8%AD%D9%88%D8%B1-%D9%82%D8%B3%D9%85%D8%AA-%D8%B3%D9%88%D9%85-nwm1js58fqal</link>
                <description>قسمت های قبلی: https://vrgl.ir/ez4b2  https://vrgl.ir/u5BLy در قسمت دوم، یک ماژول تعریف کردیم که قراره داخل این قسمت، وابستگی هارو بهش اضافه کنیم.برای شروع، بیس یو ار ال ها رو تامین می کنیم:@Provides
@Singleton
fun provideIceAndFireBaseURL(): String = &amp;quothttps://www.anapioficeandfire.com/&amp;quot

@Provides
@Singleton
fun provideQuotesBaseURL(): String = &amp;quothttps://got-quotes.herokuapp.com/&amp;quotخب الان ی مشکلی پیش میاد ? چون که از ما از دو وب سرویس متفاوت استفاده می کنیم و اینکه هر دو تامین کننده، ی نوع داده رو بازگشت میدن و باید به دگر بفمهمونیم که کجا از کدوم بیس یو ار ال استفاده کنه! اینجاست که ما از  Qualifier استفاده می کنیم.در پوشه di-&gt; annotation یک فایل به نام Annotations ایجاد می کنیم:@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IceAndFire

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Quotesحالا انوتیشن IceAndFire و Quotes رو به بیس یو ار ال های خودمون اضافه می کنیم:@IceAndFire
@Provides
@Singleton
fun provideIceAndFireBaseURL(): String = &amp;quothttps://www.anapioficeandfire.com/&amp;quot

@Quotes
@Provides
@Singleton
fun provideQuotesBaseURL(): String = &amp;quothttps://got-quotes.herokuapp.com/&amp;quotبرای مکانیزم کش و کلاینت کاستوم شده okHttp، به کلاس NetworkHelper نیاز داریم، و کلاس NetworkHelper هم برای کار، به Context نیاز داره! به این صورت تعریفش می کنیم:@Provides
@Singleton
fun provideNetworkHelper(@ApplicationContext context: Context): NetworkHelper = NetworkHelper(context)از انوتیشن ApplicationContext برای تزریق Context به NetworkHelper استفاده می کنیم. حالا کلاینت OkHttp رو تعریف می کنیم:@Provides
@Singleton
fun provideOkHttpClient(@ApplicationContext context: Context, networkHelper: NetworkHelper): OkHttpClient {

    val cacheSize = (5 * 1024 * 1024).toLong()
    val myCache = Cache(context.cacheDir, cacheSize)

    return OkHttpClient.Builder()
        .cache(myCache)
        .addInterceptor { chain -&gt;
            var request = chain.request()
            request = if (networkHelper.isNetworkConnected())
                request.newBuilder().header(&amp;quotCache-Control&amp;quot, &amp;quotpublic, max-age=&amp;quot + 5).build()
            else
                request.newBuilder().header(&amp;quotCache-Control&amp;quot, &amp;quotpublic, only-if-cached, max-stale=&amp;quot + 60 * 60 * 24 * 7).build()
            chain.proceed(request)
        }
        .build()
}دوباره برای تزریق Context از ApplicationContext استفاده می کنیم.حالا برای تامین Retrofit، چون دو بیس یو ار ال داریم، پس باید دو آبجکت از رتروفیت تعریف کنیم:@IceAndFire
@Provides
@Singleton
fun provideIceAndFireRetrofit(client: OkHttpClient, @IceAndFire baseUrl: String): Retrofit {
    return Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(baseUrl)
        .client(client)
        .build()
}

@Quotes
@Provides
@Singleton
fun provideQuotesRetrofit(client: OkHttpClient, @Quotes baseUrl: String): Retrofit {
    return Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(baseUrl)
        .client(client)
        .build()
}در اینجا دوباره از انوتیشن IceAndFire و Quotes استفاده کردیم که به دگر بفهمونیم در کجا از کدومش استفاده می کنیم! اگه دقت کرده باشید برای پارامتر بیس یو ار ال در هر دو فانکشن، مشخص کردیم که کدوم بیس یو ار ال رو میخوایم.حالا نوبت میرسه به تامین کلاس های نتورک و ApiHelper های اپلیکیشنه:@Provides
@Singleton
fun provideIceAndFireApiService(@IceAndFire retrofit: Retrofit) = retrofit.create(IceAndFireApiService::class.java)

@Provides
@Singleton
fun provideIceAndFireApiHelper(@Quotes retrofit: Retrofit) = retrofit.create(QuotesApiService::class.java)


@Provides
@Singleton
fun provideQuotesApiService(apiImpl: IceAndFireApiImpl): IceAndFireApiHelper = apiImpl

@Provides
@Singleton
fun provideQuotesApiHelper(apiImpl: QuotesApiImpl): QuotesApiHelper = apiImplخب، ماژول ما تموم شد و کد کامل شده:@Module
@InstallIn(ApplicationComponent::class)
class IceAndFireModule {

    @IceAndFire
    @Provides
    @Singleton
    fun provideIceAndFireBaseURL(): String = &amp;quothttps://www.anapioficeandfire.com/&amp;quot

    @Quotes
    @Provides
    @Singleton
    fun provideQuotesBaseURL(): String = &amp;quothttps://got-quotes.herokuapp.com/&amp;quot

    @Provides
    @Singleton
    fun provideNetworkHelper(@ApplicationContext context: Context): NetworkHelper = NetworkHelper(context)

    @Provides
    @Singleton
    fun provideOkHttpClient(@ApplicationContext context: Context, networkHelper: NetworkHelper): OkHttpClient {

        val cacheSize = (5 * 1024 * 1024).toLong()
        val myCache = Cache(context.cacheDir, cacheSize)

        return OkHttpClient.Builder()
            .cache(myCache)
            .addInterceptor { chain -&gt;
                var request = chain.request()
                request = if (networkHelper.isNetworkConnected())
                    request.newBuilder().header(&amp;quotCache-Control&amp;quot, &amp;quotpublic, max-age=&amp;quot + 5).build()
                else
                    request.newBuilder().header(&amp;quotCache-Control&amp;quot, &amp;quotpublic, only-if-cached, max-stale=&amp;quot + 60 * 60 * 24 * 7).build()
                chain.proceed(request)
            }
            .build()
    }

    @IceAndFire
    @Provides
    @Singleton
    fun provideIceAndFireRetrofit(client: OkHttpClient, @IceAndFire baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(baseUrl)
            .client(client)
            .build()
    }
    @Quotes
    @Provides
    @Singleton
    fun provideQuotesRetrofit(client: OkHttpClient, @Quotes baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(baseUrl)
            .client(client)
            .build()
    }


    @Provides
    @Singleton
    fun provideIceAndFireApiService(@IceAndFire retrofit: Retrofit) = retrofit.create(IceAndFireApiService::class.java)

    @Provides
    @Singleton
    fun provideIceAndFireApiHelper(@Quotes retrofit: Retrofit) = retrofit.create(QuotesApiService::class.java)

    @Provides
    @Singleton
    fun provideQuotesApiService(apiImpl: IceAndFireApiImpl): IceAndFireApiHelper = apiImpl

    @Provides
    @Singleton
    fun provideQuotesApiHelper(apiImpl: QuotesApiImpl): QuotesApiHelper = apiImpl
}و برای تست میریم که از ویومدل داخل اکتیویتی خودمون استفاده کنیم، قبل از هر کاری انوتیشن AndroidEntryPoint رو به اکتیویتی خودمون اضافه میکنیم:@AndroidEntryPoint
class MainActivity : AppCompatActivity()با اینکار به دگر اجازه میدیم که وابستگی های درخواستی رو به اکتیویتی تزریق کنه. از انوتیشن AndroidEntryPoint در موارد زیر استفاده میشه:اکتیویتیفرگمنت سرویسویوبرادکست رسیوربرای تزریق ویومدل در داخل اکتیویتی به این صورت عمل می کنیم:private val vm by viewModels&lt;MainViewModel&gt;()و یکی از لایودیتاها رو مشاهده می کنیم:vm.quote.observe(this, Observer { 
    Log.e(&amp;quotqoute&amp;quot,it.toString())
})کار ما تمام شد و با موفقیت دگر هیلت رو پیاده سازی کردیم!سورس کد پروژه در گیتهاب موجوده و در ادامه برای استفاده از ابزارهایی نظیر MotionLayout و... از این کد استفاده می کنیم که به یک اپلیکیشن کامل تبدیل بشه!</description>
                <category>موبایل لَبْ</category>
                <author>محمد معین عبدی</author>
                <pubDate>Fri, 31 Jul 2020 23:35:34 +0430</pubDate>
            </item>
            </channel>
</rss>