<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های مرتضي درزي</title><link>https://virgool.ir/feed/@morteza.darzi</link><description>برنامه نویس فرانت ( ریکت )</description><language>fa</language><pubDate>2026-11-11 10:24:29</pubDate><image><url>https://files.virgool.ir/upload/users/27866/avatar/JK06LH.png</url><title>مرتضي درزي</title><link>https://virgool.ir/@morteza.darzi</link></image><item><title>یادداشتی طنز</title><link>https://virgool.ir/@morteza.darzi/یادداشتی-طنز-uxmkhsxdx2vn</link><description>سالها پیش یه وبلاگ تو بلاگفا داشتم که مطالبی از همه جا و هر چی توش مینوشتم به نام یادداشتی . بیشتر جنبه شخصی داشت تا محتوای خاص . داشتم نوشته های یادداشتی رو مرور میکردم به این مطلب طنز که تو سال 92 نوشتم برخوردم که در اون حال هوای تحریم و برجام و ظریف و ازدواج به نظرم جالب اومد . من و ظریفداستان من و ظریف خیلی طول و درازه ، ظریف رو که میشناسید ؟ اره همین ظریف ۶ نفر حریف !۴ سال پیش که برای بار دوم یه معدوم الحال رو انتخاب کردیم و بعدش هم اون همه دعوا ، من هیچ به چیزی به نام ازدواج فکر هم نمی کردم ، یه وقتی هم صحبتی میشد جز سرخ شدن کاری نمی شد کردبعد دو سال که از ریاست اون اغا گذشت ، یکدفعه جهان با ما دشمن شد . از داخل هم هی گفتن دشمنان اسلام هیچ غلطی نمی تونن بکنن ، اینقدر بدید تا قعطنامه دونتون پاره بشه که ما فکر کردیم الان حتما &#34; طیرن ابابیل &#34; میشه و این گنجیشک ها میرن امریکا رو میبرن زیر اب !تو اون اوضاع هشل هفت ، دیدم اِ مقوله ی مهمی به نام ازدواج هم شتری شده که داره میخوابه روم ، ولی مگه میشد به چیزی فکر کرد ؟ هر ان امکان داشت لشکر اسلام ذوالفقار از عبای ولی فقیه بکشه بیرون و نفس کش کنان عبدود زمان رو از وسط به دو نیم مساوی تقسیم کنه بطوری که اگه دو نیم رو وزن کنی گرم نزنه ، ملت سرمست از پاره شدن های قعطنامه شورای امنیت در حال عیش و نوش مستی بودند.خلاصه ما هم نیست خجالتی از حالت سرخ شدن به سر جنبندن تغییر وضع دادیم . حالا خودمونیم گه گداری بهش فکر هم میکردیم :-D . ماجرا گذشت تا دو سال اخر و شتر هم داشت دیگه خسته میشد که بخوابه که کاشف به عمل اومد که اون چیزایی که داشت پاره میشد ماتحت ما بود نه قعطنامه شورای امنیت ! تا بیایم بجنبیم که کی بود کی نبود ، گفتن وایستید هوا عوض میشهجونم براتون بگه که تو این دوران اینقدر هشت ها گروه نه شد که کسی جرات نمی کرد از شتر حرف بزنه ، ما هم در سکوت معنا داری فرو رفتیم . اوضاع اقتصادی بدجور خراب بود ، به حقوق مون یک میلیون ریال وجه رایج مملکت اضافه شده بود ولی هر شب اگه از سفرمون عکس میگرفتی ، فکر میکردی یه سری غذا دارن انیمیشن بازی میکنن و از این سر سفره میرن اون سر ، ملت برا چهل و پنج تومن ناقابل بشکن میزدن ، خودم دیدم به خدا  !!!!همونطور که وعده خدا حقه ! هوا عوض شد ، فضا روحانی شد ، ظریف کوچولو ، توپولوی ما هم اومد . اوضاع کلی تغییر کرد ، ما هم به امید تغییر ، تغییر کردیم . از اینجا دیگه من و ظریف با هم بودیم ظریف میومد فیس بوق پست میذاشت ( این خودش یه تغییر اساسی بود ) ، منم به گزینه های دیگران یکم فکر میکردم ( اینم یه تغییر اساسی بود ) . ظریف رفت با امریکا دست داد و طلسم رو شکست ، منم رفتم در مورد یکی دو نفر فکر اساسی کنم طلسم بختشون وا شد دی:ظریف کاری کرد دل اوباما هنو روحانی نرفته تنگ بشه ، من یه گزینه گذاشتم روی میز ! ، ظریف قول داد مذاکره کنه و تحریم ها رو بشکنه ، من قول دادم به پیشنهاد هایی که میشه بیشتر فکر کنم وگرنه پیشنهاد دهندگان از کار خودشون پشیمون میشن .بهر حال گذشت و گذشت تا اینکه صبح یه روز از رادیو شنیدیم ظریف توافق کرد ! حالا ظریف رو بروی من نشسته و با اون لبخند ملیحش میگه : بدو وقت نداری ، نوبت توه وگرنه میرم اشتون رو برات میگیرم ( ایش ، مور مور شدم )</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sun, 24 Apr 2022 11:24:47 +0430</pubDate></item><item><title>گوگل مرد !</title><link>https://virgool.ir/@morteza.darzi/گوگل-مرد-jv10hpnp3iny</link><description>کمک کد نویس GitHub Copilotاگر برنامه نویس باشید ، حتما در مورد GitHub Copilot شنیدید یا دارید ازش استفاده میکنید . کمک کد نویسی که با استفاده از هوش مصنوعی کد های شما رو تکمیل میکنه . اون به هیچ چیزی رحم نمی کنه ! کامنت ، اسم تابع ، کدی که چند خط بالاتر نوشتید و هر چیزی که میتونه کمک کنه بفهمه چی دارید مینویسید . چند وقت بود که در مورد همکاری OpenAI و Github شنیده بودم و میشد حدس زد که وقتی دو غول بزرگ در دنیای برنامه نویسی شروع به همکاری میکنند چیزی که ازش در میاد باید خیلی فوق العاده باشه خصوصا که موضوع هوش مصنوعیه . یه روز بطور اتفاقی تو اینستاگرام دیدم که ازش دارن استفاده میکنن و به سایتش سری زدم دیدم که میشه بری تو لیست انتظار و خوب منم سریع ثبت نام کردم .&#34; گلام&#34; ( شخصیت کارتونی گالیور - من میدونم ما ... &#34;  درونم بهم میگفت ما این گوشه دنیا اونم در کشوری که تحریمه مگه میشه به این زودی ها از این ابزار بتونیم استفاده کنیم ؟ . ولی کمتر از 5 روز بعد ( دقیق نمی دونم ) ایمیل اومد و ابزار فعال شد . طبق گفته سایت اصلی :Extends your editorGitHub Copilot is available as an extension for Neovim, JetBrains, and Visual Studio Code.فعلا میشه از این ابزار روی Neovim, JetBrains,Visual Studio Code استفاده کرد و با اینکه روی JetBrains کار میکنه ولی تا این تاریخ ( 1400/12/03 ) هنوز روی Android Studio فعال نشده (  به زودی فعال میشه )برای من که برنامه نویس اندروید و فلاتر هستم ، اندروید استودیو تقریبا خدا محسوب میشه و با توجه به نیاز شرکت جدیدم به اپلیکیشن موبایلی با فلاتر بیشتر مجاب شدم به VS Code مهاجرت کنم و از این ابزار هم استفاده کنم . بازم &#34; گلام &#34; اومد و گفت اینو بخون :Speaks all the languages you loveGitHub Copilot works with a broad set of frameworks and languages. The technical preview does especially well for Python, JavaScript, TypeScript, Ruby, Java, and Go,این یعنی Copilot به Dart کمکی نمیکنه . اما وقتی شروع به نوشتن اولین فایل دارت برای پروژه کردم در کمال نا باوری همه چیز رو درست حدس میزد حتی زمانی که داشتم widget میساختم ! . بله Copilot از روی پروژه های public روی github مدل سازی هوش مصنوعیش رو می سازه و هر زبانی که تو Github کدی براش نوشته شده باشه رو میتونه تکمیل کنه . روش کار با این &#34; دیونه دوست داشتنی &#34; هم زیاد سخت نیست ، چون اصلا روشی نداره . فقط کافیه بنویسی که چی میخوای . اما اصولیش اینه که کدی که میخوای رو بصورت کامنت بنویسی و یه enter ساده بزن و منتظر باش تا جادو اتفاق بیافته . حتما تعجب خواهید کرد که چطور میتونه درست حدس بزنه !؟ Copilot با استفاده از اسم فایلی که دارید توش کد رو مینویسید و کد هایی که از قبل تو همین فایل نوشتید و کامنتی که ذکر کردید و اسم هایی که تو کامنت استفاده کردید و کد های Github بهترین گزینه رو براتون میاره .کار به همینجا هم ختم نمیشه . شاید فکر کنید که Copilot یه قطعه کد رو براتون تکمیل میکنه ولی نه ، اون همه چیز رو تکمیل میکنه ، حتی وقتی دارید کامنتی مینویسید که Copilot ازش استفاده کنه هم خودش بهتون پیشنهاد میده . این ابزار خوش دست به هر نوشته ای حساسه و هر چیزی که بنویسید رو میتونه تکمیل کنه اما داستان زمانی غم انگیز ( شایدم هیجان انگیز ) میشه که بعد از ساعت ها کار کردن با این ژیگولی همه فن حریف و عشق بازی باهاش (  بعضی وقت ها احساس میکنم Copilot اونجا هست و باهاش حرف میزنم دی: ) یکهو به خودت میای میبینی هیچ مرورگری باز نیست !! من پنج ساعت کد زدم و اصلا گوگل رو باز نکردم ! من کد هایی زدم که حتی نمی دونستم وجود دارند و اینکار رو بدون استفاده از گوگل کردن انجام دادم !داستان بازم هیجان انگیزتر میشه وقتی با یه زبان جدید بخوای کد بزنی . من که از قبل آشنایی کوتاهی با جاوا اسکریپ داشتم ، پروژه ای تو شرکت برام تعریف شد که برای یه قسمت سایتمون باید Speech To Text رو درست میکردم . برنامه نویس ها میدونن استرس شروع پروژه جدید بدون دانش کافی چقدر زیاده و خوب تنها امیدواریت اینه که گوگل هست ( حتی رئیس شرکت هم به گوگل اطمینان داره وقتی میگه گوگلش کن حله ) . اما من با Copilot تقریبا 30 درصد مثل همیشه از گوگل استفاده کردم و چیزی رو که میخواستم Copilot خودش برام مینوشت ! کافی بود یه فایل  speechRecognation.js بسازم و کامنتی بنویسم که میخوام speech to text رو بسازم و Copilot تمام ، کد ها ساخته شد و حتی بهم پیشنهاد داد که خروجی رو چجوری روی صفحه نشون بدم !!یاد حرف نیچه افتادم که میگفت &#34; خدا مرد &#34; . شاید این حرف زود باشه ولی احساس میکنم &#34; گوگل برای ما برنامه نویس ها داره میمیره &#34; شایدم حالش بد شده دی: . صد البته که هنوز خیلی بهش نیاز داریم و خواهیم داشت و برنامه نویسی فقط کد نیست و باید مفاهیم رو هم درک کنیم ولی تجریه شخصی من میگه استفاده از گوگل 60 درصد کمتر شده ! روند کد نوشتن به این صورت بوده که شما چیزی رو میخواستید در گوگل سرچ میکردید و گوگل بهترین نتایج رو براتون می اورد . بعد شما نتیجه رو میخوندید و تبدیل به کد میکردید . حالا کافیه اونچیزی رو که میخواهید بنویسید تا Copilot براتون خودش سرچ کنه و بهترین کدش رو بهتون پیشنهاد بده و این اصلا وحشتناک نیست .ترس از هوش مصنوعی و بیکار شدن ما برنامه نویس ها خیلی بی معنیه ! برنامه نویسی فراتر از کد نویسیه و روش حل مساله است . چه نیازیه که من به عنوان برنامه نویس بجای خلق ویژگی جدید در برنامه وب شرکتمون ، دنبال نحوه استفاده درست از WebSpeechRecognation  باشم . اتفاقا یکی مثل Copilot باید می اومد که جلوی هدر رفت زمان هایی زیادی برای فهم کار با کد هایی که نوشته شده رو ازمون میگرفت و نوشتن یه برنامه Speech To Text تو سال 2021 رو برای یه برنامه نویس نیمه آشنا با جاوا اسکریپ به یک روز کاهش میداد و این کاملا هیجان انگیزه نه وحشتناک ! این کاملا هیجان انگیزه که بنویسم //random date before now to 2 yearsو فارغ از زبانی که دارم برنامه نویسی میکنم Copilot بهم بگه چجوری از توابع Date باید استفاده کنم . مثلا نمونه کدی که در زبان dart پیشنهاد داد :DateTime.now().subtract(Duration(days: Random().nextInt(730)))میدونم و میدونید سرچ کردن و استفاده درست از تابع subtract حداقل نیاز به 10 دقیقه زمان داشت ولی من اصلا نمی دونستم چنین تابعی هست و اصلا هم نیاز نداشتم اینقدر زمان بزارم و با Copilot دقیقا فهمیدم چه کاربری داره این تابع . همین یعنی خود برنامه نویسی</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Tue, 22 Feb 2022 11:19:14 +0330</pubDate></item><item><title>تمرین Jetpack Compose 2</title><link>https://virgool.ir/@morteza.darzi/تمرین-jetpack-compose-2-llm8ty8ic9qi</link><description>  https://virgool.io/@morteza.darzi/%D8%AA%D9%85%D8%B1%DB%8C%D9%86-jetpack-compose-1-yve0exmsijos در مطلب قبل پروژه ی جدید کامپوز رو شروع کردیم و تغییراتی توش اعمال کردیم و دیدیم که چقدر کامپوز کارها رو راحت کرده . حالا میخوایم بیشتر با کامپوز آشنا بشیمJetpack Compose Basic Tutorial&#xA;رابط کاربری در برنامه های اندروید باید داده هایی رو به کاربر نشون بدن و این داده ها همیشه از اول برنامه ثابت نیست و در طول اجرای برنامه تغییر میکنه . کامپوز به این داده ها میگه وضعیت State . اما وضعیت چیه ؟وضعیت در کامپوز :همونطور که دیدیم توابع کامپوز ، اطلاعات رو گرفته و در صفحه ، نمایش میدن . حالا اگر اطلاعات تغییر کنن ، کامپوز اتوماتیک میره اون تابعی که اطلاعات رو میگرفت و نشون میداد رو صدا میزنه و بهش میگه بیا داده ات تغییر کرده و خودت رو با داده جدید به روز کن ! ، که ما به این کار میگیم Recomposing و کامپوز وقتی میتونه چنین کاری کنه که اطلاعات تغییر پذیر رو به صورت وضعیت state براش تعریف کنیم . البته کامپوز میدونه کدوم عناصر به این داده احتیاج دارن و فقط همونها رو به روز میکنه و اینکار سرعت تغییر در رابط کاربر رو بسیار بالا میبره .برای اضافه کردن وضعیت در کامپوز باید از توابع mutableStateOf استفاده کنیم ، که میشه گفت یک حافظه قابل تغییر کامپوز شده است و برای اینکه وضعیت رو ذخیره شده داشته باشیم باید از remember استفاده کنیم . حالا عناصر کامپوز در جاهای مختلف صفحه وضعیت رو بصورت متغییر داخل خودشون استفاده میکنن  و بصورت خودکار هر وقت Recomposing اتفاق بیافته وضعیت جدید رو میگیرن و صفحه نمایش رو به روز میکنن .برای تمرین یک شمارنده میسازیم که تعداد دفعات که کاربر روی Button  کلیک کرده رو میشماره . شمارنده ما از یک Button  تشکیل شده که محتوای داخل اون با یک Text تعداد دفعات کلیک شدن رو نشون میده و متد  برای Button  تعداد کلیک شدن رو افزایش میده :@Composable&#xA;fun Counter() {&#xA;    val count = remember { mutableStateOf(0) }&#xA;    Button( = { count.value++ }) {&#xA;        Text(&#34;I&#39;ve been clicked ${count.value} times&#34;)&#xA;    }&#xA;}همونطور که میبینیم count با استفاده از remember و mutableStateOf با مقدار اولیه صفر ، بصورت وضعیت در کامپوز تعریف شده . این یعنی وقتی count تغییر کنه ( در این مثال وقتی کاربر روی Button کلیک میکنه ++count.value ) هر کی ازش استفاده کرده ( در این مثال Text )  باید مقدار جدید رو نشون بده و اصطلاحا Recomposing اتفاق می افته . بیاید با هم شمارنده رو به پروژه قبلیمون اضافه میکنیم @Composable&#xA;fun MyScreenContent(names: List&lt;String&gt; = listOf(&#34;Android&#34;, &#34;there&#34;)) {&#xA;    Column {&#xA;        for (name in names) {&#xA;            Greeting(name = name)&#xA;            Divider(color = Color.Black)&#xA;        }&#xA;        Divider(color = Color.Transparent, thickness = 32.dp)&#xA;        Counter()&#xA;    }&#xA;}و اینم خروجی ، دکمه ای که با کلیک روش نوشته اش تغییر میکنهخروجی پروژه جدیداستفاده کاربردی از وضعیتوضعیت ها در کامپوز باید جایی تعریف بشن که تخصیص اطلاعات و به روز رسانی اونها راحتر باشه . ما در کامپوز که پر است از توابع تو در تو و پیچیده ، سعی میکنیم وضعیت ها رو در سطح توابع بالاتر تعریف کنیم و آنها رو بصورت پارامتر به تابع کامپوز بفرستیم تا کنترل بهتری روش داشته باشیم ، به اینکار state hoisting می گن . با تعریف وضعیت ها در سطح بالاتر عملا از وضعیت های تکراری و ایرور ها جلو گیری میکنیم و همچنین امکان به روز رسانی چند تابع کامپوز رو با هم با یک وضعیت از قبل تعریف شده در تابع بالاتر فراهم میکنیم . اینکار در عین حال قابلیت استفاده مجدد و تست پذیری کد های کامپوز رو بالا میبرهبه عنوان تمرین میخوایم وضعیت شمارش کلیک توسط کاربر رو در پروژه مون بالا ببریم یا اصطلاحا  state hoisting کنیم. یعنی تابع کامپوز Counter ما ، متغییر count رو به عنوان پارامتر دریافت خواهد کرد . علاوه بر خود وضعیت در این مثال ، با استفاده قابلیت ارسال تابع به عنوان پارامتر ، حتی متد تغییر دهنده وضعیت رو هم بالا میبریم و به عنوان پارامتر به Counter میفرستیم . عملا با اینکار کنترل بهتری روی وضعیت و نحوه تغییر وضعیت خواهیم داشت . تابع Counter ما خیلی خوب قابلیت استفاده مجدد خواهد داشت و تست کردن چنین تابعی هم خیلی راحته @Composable&#xA;fun Counter(count: Int, updateCount: (Int) -&gt; Unit) {&#xA;    Button( = { updateCount(count+1) }) {&#xA;        Text(&#34;I&#39;ve been clicked $count times&#34;)&#xA;    }&#xA;}اینجوری هم این تابع رو استفاده میکنیم . به نحوه ارسال متد به عنوان پارامتر توجه کنید .@Composable&#xA;fun MyScreenContent(names: List&lt;String&gt; = listOf(&#34;Android&#34;, &#34;there&#34;)) {&#xA;    val counterState = remember { mutableStateOf(0) }&#xA;    Column {&#xA;        for (name in names) {&#xA;            Greeting(name = name)&#xA;            Divider(color = Color.Black)&#xA;        }&#xA;        Divider(color = Color.Transparent, thickness = 32.dp)&#xA;        Counter(&#xA;            count = counterState.value,&#xA;            updateCount = { newCount -&gt;&#xA;                counterState.value = newCount&#xA;            }&#xA;        )&#xA;    }&#xA;}به همین سادگی میشه توابع کامپوزی ساخت که خاصیت تست پذیری و استفاده مجدد بالایی داشته باشن . این مطلب ادامه دارد ....</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sat, 14 Aug 2021 13:34:00 +0430</pubDate></item><item><title>تمرین Jetpack Compose 1</title><link>https://virgool.ir/@morteza.darzi/تمرین-jetpack-compose-1-yve0exmsijos</link><description> Jetpack Compose Basic Tutorialکامپوز چیه ؟در مطلب قبل گفتیم که Jetpack Compose ( از این به بعد بهش میگیم کامپوز ) در سال 2019 معرفی شده و الان در ورژن 1.0 استیبل شده و در اختیار توسعه دهندگان قرار گرفته . اما واقعا کامپوز اومده که چه کاری رو برامون راحتر کنه ؟ کامپوز کتابخونه ایه که روش طراحی رابط کاربری رو تغییر داده . در روش قبلی ( View ) علاوه بر اینکه باید تعیین میکردیم چه چیزی در صفحه ، نمایش داده بشه ( با تعریف xml ) ، باید چگونگی نمایش دادن رو هم تعیین میکردیم ( کد نویسی در اکتیویتی با R.id.viewId ) . ولی در کامپوز فقط توصیف میکنیم چه چیزی میخوایم و بقیه رو کامپوز خودش انجام میده !! چه خوب ، بزارید یه مثال خیلی جالب برای مقایسه دو روش بزنم . فرض کنید از کسی میخوایم که برامون چای بیاره . در روش اول بهش میگیم &#34; برو سماور روشن کن ، چای بریز تو قوری و آب جوش بریز و ....&#34; ، ولی در روش دوم بهش میگیم &#34; یکم چای با شکر &#34; برام بیار و بقیه رو خودش انجام میده . به این روش میگن توصیفی یا اعلامی ( declarative )برای اینکه بتونیم بیشتر با کامپوز آشنا بشیم یه پروژه کوچیک رو به عنوان تمرین شروع میکنیم تا ببینیم چقدر انجام همه کارهایی که قبل از این نیاز به کد نویسی زیادی داشت به سادگی با کامپوز قابل انجامه . در این تمرین از مطالب سایت گوگل استفاده شدهنیازمندی ها :قبل از هر چیز باید ورژن جدید اندروید استودیو Arctic Fox رو از لینک زیر دانلود کنید . این ورژن تمام قابلیت های مورد نیاز برای استفاده از کامپوز رو در خودش داره Android Studio Arctic Foxساخت پروژه :پس از نصب و اجرای اندروید استودیو  روی New Project کلیک کنیدصفحه اولیه اندروید استودیوحالا کافیه Empty Compose Activity رو انتخاب کنید انتخاب نوع پروژهاسمی برای پروژتون انتخاب کنید و minimumSdkVersion رو 12 قرار بدید ( کامپوز از اندروید 12 به بالاتر کار خواهد کرد ) . اندروید استودیو فایل های مورد نیاز رو برای پروژه آماده میکنه و حالا همه چیز آماده است فقط یه کار مونده . فایل Gradle باز کنید و در قسمت composeOptions خط kotlinCompilerVersion  رو حذف کنید .  composeOptions {&#xA;        kotlinCompilerExtensionVersion compose_version&#xA;        // Remove it -&gt; kotlinCompilerVersion  &#39;version&#39;&#xA;    }علت اینکار اینه که کامپوز در ورژن جدید در buildscript از پلاگین کاتلین استفاده میکنه و این خط کد دیگه منسوخ ( deprecated ) شده ، میتونید از اینجا اصل مطلب رو بخونیدبررسی فایل های پروژه :خوب همه چیز آماده است . بزارید با هم فایل MainActivity.kt رو بررسی کنیم . در بالای فایل مثل همیشه اکتیویتی اصلی برنامه مثل قبل دست نخورده مونده . قسمت جدید setContent به پروژه اضافه شده که دروازه ورود کامپوز به پروژه است ، یعنی هرچیزی که بخواهیم با کامپوز به پروژه اضافه کنیم به این قسمت اضافه خواهد شدclass MainActivity : AppCompatActivity() {&#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;        super.onCreate(savedInstanceState)&#xA;        setContent {&#xA;            BasicsCodelabTheme {&#xA;                Surface(color = MaterialTheme.colors.background) {&#xA;                    Greeting(&#34;Android&#34;)&#xA;                }&#xA;            }&#xA;        }&#xA;    }&#xA;}قطعه کد BasicsCodelabTheme تم اصلی برنامه رو معرفی میکنه که در فایل ui/Theme.kt قرار داره و اسمش بصورت اتوماتیک از روی اسم پروژه  توسط اندروید استودیو ساخته شده و میتونید به هر اسمی تغییرش بدید . در قسمت های بعدی بیشتر به استایل دهی و تم خواهیم پرداخت .تابع Greeting هم خود کامپوزه ، همون چیزی که توصیف کردیم تا در صفحه ، نمایش داده بشه@Composable&#xA;fun Greeting(name: String) {&#xA;    Text(text = &#34;Hello $name!&#34;)&#xA;}همانطور که ساختار تابع کامپوز رو میبینید ، یک تابع معمولی کاتلینه ، که با Composable@ مزین شده ! و میشه توابع دیگر کامپوز رو توش صدا زد. همانطور که در تابع Greeting می بینید ، یک String به عنوان پارامتر دریافت میکنیم و اون رو در یک تابع کامپوز تعریف شده Text نشون میدیم. بدلیل استفاده از کاتلین در کامپوز به راحتی میشه از همه قابلیت های کاتلین در طراحی کامپوز استفاده کرد و نحوه نوشتن اون به سادگی نوشتن یک متد در کاتلینه و در مقایسه با کد های XML که برای layout در روش قبلی استفاده میشده بسیار ساده انعطاف پذیرتره .@Preview(showBackground = true)&#xA;@Composable&#xA;fun DefaultPreview() {&#xA;    BasicsCodelabTheme {&#xA;        Greeting(&#34;Android&#34;)&#xA;    }&#xA;}اما میرسیم به قسمت جذاب کامپوز یعنی پیش نمایش . اینجا هم با یک تابع ساده کاتلین کامپوز شده ( تزئین شده با Composable@ ) طرفیم که یک Preview@ هم بهش اضافه شده . این تابع به اندروید استودیو میگه که میخوایم چیزی رو پیش نمایش بدیم و پنجره جدید Compose Preview برامون باز میکنه . کافیه از کامپوز هایی که در پروژه استفاده کردیم به این تابع اضافه کنیم تا در پیش نمایش دیده بشن ، فقط یادتون باشه که باید برای پارامتر های توابع حتما مقدار رو تعیین کنید . ما هم در این مثال از همون تابع Greeting استفاده کردیم و تنها کاری که میمونه اینه که پروژه رو build کنیم و ببینیم چی ساختیم پیش نمایش کد کامپوزهمچنین هر بار که تغییری در کد کامپوز ایجاد کنیم ، دکمه Build &amp; Refresh در قسمت preview نمایش داده میشه که با کلیک روی اون پیش نمایش بروز خواهد شد .پارامتر Modifiers :اکثر عناصر کامپوز یک پارامتر اختیاری modifier دارند . پارامتر modifier تنظیمات مختلفی رو روی عناصر کامپوز اجرا میکنه ، مثل جایگیری روی صفحه یا نحوه نمایش یا رفتار اونها . می تونید اونها را به متغیرها اختصاص بدید و در جاهای مختلف ازش استفاده کنید. همچنین می توانید چندین modifier ها را یکی پس از دیگری با استفاده از توابع factory-extension یا با تابع then بهم متصل کنید . فقط یادتون باشه که ترتیب اجرای modifier ها همونجوری که نوشته میشن از بالا اولین modifier تغییرات رو روی عناصر اعمال خواهد کرد . برای مثال پارامتر padding modifier  فضایی خالی را در اطراف عناصر اعمال می کنه .برای اینکار modifier  را به Text خود اضافه کنید:        Text(text = &#34;Hello $name!&#34;, modifier = Modifier.padding(24.dp))    padding modifierکامپوننت های کامپوز همینطور که کامپوز های جدید را به صفحه اضافه میکنیم کد ها بصورت تو در تو ادامه پیدا میکنن و هر چه که کد ما بزرگتر میشه درک اون پیچیده تر شده و امکان استفاده از اون در جاهای دیگه هم کمتر میشه . طبق اصول Clean Code ما باید کد هامون رو جوری طراحی کنیم که به قسمت های کوچیک با ساختار و خروجی درست تقسیم بشه و در کامپوز هم باید سعی کنیم با ساخت کامپوز بصورت کامپوننت های کوچک بتونیم قابلیت استفاده مجدد کد را بالا ببریم و سرعت تست پذیری و یافتن خطا در اون رو هم افزایش بدیم به عنوان اولین تغییر بگذارید کل رابط کاربری این اکتیویتی را به یک متد کامپوز MyApp انتقال بدیم . class MainActivity : AppCompatActivity() {&#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;        super.onCreate(savedInstanceState)&#xA;        setContent {&#xA;            MyApp()&#xA;        }&#xA;    }&#xA;}@Composable&#xA;fun MyApp() {&#xA;    BasicsCodelabTheme {&#xA;        Surface(color = Color.Yellow) {&#xA;            Greeting(name = &#34;Android&#34;)&#xA;        }&#xA;    }&#xA;}@Composable&#xA;fun Greeting(name: String) {&#xA;    Text(text = &#34;Hello $name!&#34;, modifier = Modifier.padding(24.dp))&#xA;}@Preview&#xA;@Composable&#xA;fun DefaultPreview() {&#xA;    MyApp()&#xA;}متد کامپوز MyApp میتونه به عنوان بدنه اصلی تشکیل دهنده رابط کاربری در جاهای دیگه هم استفاده بشه ، ولی یک مشکل وجود داره و اون هم وجود متد Greeting در MyApp که مختص این اکتویتیه و جای دیگه بهش نیاز نداریم . پس باید MyApp رو جوری تغییر بدیم که بصورت یک کامپوننت کامل قابل حمل بشه ازش در اکتیویتی های دیگه هم استفاده کرد قالب اصلی رابط کاربری :چیزی که ما نیاز داریم یک متد عمومیه که محتوایی رو مثلا به عنوان پارامتر content دریافت کنه و در بدنه خودش تنظیمات اصلی نمای صفحه را انجام بده . اینجوری میشه محتوای صفحه رو از قالب اصلی جدا کرده و در صفحات دیگه هم از این متد عمومی هم استفاده کرد@Composable&#xA;fun MyApp(content: @Composable () -&gt; Unit) {&#xA;    BasicsCodelabTheme {&#xA;        Surface(color = Color.Yellow) {&#xA;            content()&#xA;        }&#xA;    }&#xA;}به لطف امکان افزودن تابع ، به عنوان پارامتر در توابع کاتلین ، به راحتی میتوان یک کامپوز رو بعنوان پارامتر دریافت کرد و به این صورت تنظیمات اصلی صفحه مانند تم متریال و رنگ پس زمینه و یا هر چیز دیگر را که معمولا در همه اکتیویتی ها یکسانه به این تابع اضافه کنیم و نهایتا تابع کامپوز دریافتی ( content ) را به عنوان محتوای داخل صفحه  قرار بدیم . برای استفاده از این قالب هم به اینصورت انجام میدم .class MainActivity : AppCompatActivity() {&#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;        super.onCreate(savedInstanceState)&#xA;        setContent {&#xA;            MyApp {&#xA;                Greeting(&#34;Android&#34;)&#xA;            }&#xA;        }&#xA;    }&#xA;}شاید تعجب کنید وقتی مبینید تابع کامپوز Greeting که به عنوان پارامتر به MyApp فرستاده میشه در بدنه تابع نوشته شده و در پرانتز ابتدای تابع نیامده ! این یکی از ویژگی های کاتلینه که میشه در زمان فرستادن تابعی به عنوان پارامتر به تابع دیگر آن را بجای پرانتز در خود بدنه تابع تعریف کرد و کد بالا رو میشه به این صورت هم نوشت :MyApp({ Greeting(name = &#34;َAndroid&#34;) })کامپوز و کاتلین هماهنگی خوبی با هم دارن چون کامپوز کلا با کاتلین نوشته شده و میشه ازش مثل کد های متداول استفاده کرد .مثلا فرض کنید بخوایم چند ردیف از Text  رو در صفحه نمایش بدیم . اینکار با Column  که چیزی شبیه LinearLayout هست انجام میشه و میشه با ایجاد یک حلقه for به راحتی کد های کاتلین رابط کاربری مورد نظر رو ساخت و این یعنی سادگی درک و فهم و ساخت هر آنچیزی که نیاز دارید @Composable&#xA;fun MyScreenContent(names: List&lt;String&gt; = listOf(&#34;Android&#34;, &#34;there&#34;)) {&#xA;    Column {&#xA;        for (name in names) {&#xA;            Greeting(name = name)&#xA;            Divider(color = Color.Black)&#xA;        }&#xA;    }&#xA;}این مطلب ادامه داره ......</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Mon, 09 Aug 2021 23:17:57 +0430</pubDate></item><item><title>صفر تا صد انیمیشن در اندروید</title><link>https://virgool.ir/@morteza.darzi/صفر-تا-صد-انیمیشن-در-اندروید-w82p3r5dvsdg</link><description>انیمیشن در اندرویدانیمیشن ها در طراحی رابط کاربری اندروید قدرت فوق العاده ای در ایجاد احساس تعامل با نرم افزار در کاربر ایجاد میکند .کاربر دکمه ای را در نرم افزار لمس میکند و المان ها با ایجاد کنش حرکتی نشان میدهند خواسته کاربر را درک میکنند .با استفاده از API های که در این خصوص ساخته شده ، میتوان هر چیز در در صفحه حرکت داد ، از یک دکمه ساده گرفته تا حتی تصاویر ایکون ها . انیمیشن در اندروید در سه چارچوب ارائه شده که هر کدام برای وضعیت های مختلف طراحی شده  :انیمیشن خصوصیات ( Property Animations ) :  رابط ی پر قدرت که از اندروید 3 معرفی شده و خصوصیات المان ها را انیمیت می کندانیمیشن ویو ( View Animations ) : رابطی کند و کمتر استفاده در مقابل Property Animations که View ها را انیمیت میکند انیمیشن جابجای  ( Transition Animations ) : برای اجرا در اندروید 4.4 به بالاتر ساخته شده که جابجایی بین اکتیویتی ها و فرگمنت ها را انیمیت می کنددر این چارچوب ها 5 قالب انیمیشن قابل طراحی است و تمام کلاس ها و رابط های برنامه نویسی طراحی شده در این  قالب ها جای میگیرند :انیمیت خصوصیت ( Property Animations ) : این انیمیت برای تغییر یک خصوصیت بین دو مقدار می باشد و معمولا برای انیمیت کردن view ها در صفحه با چرخش و تغییر مکان و غیره انجام میگیرد انیمیت جابجایی اکتیویتی ( Activity Transitions ) : این انیمیت در زمان ورود یا خروج اکتیویتی از صفحه اجرا میشود . انیمیت جابجایی فرگمنت ( Fragment Transitions ) : این انیمیت در زمان جابجایی بین فرگمنت ها در صفحه اجرا میشود . انیمیت لی اوت ( Layout Animations ) : این انیمیت روی لی اوت ها یا ViewGroup ها اجرا میشود و تمام المان های داخل لی اوت را انیمیت می کند .انیمیت تصاویر ( Drawable Animations ) : این انیمیت برای تغییر تصاویر drawable بصورت انیمیشن اجرا میشود .انیمیشن Property Animationsاین نوع انیمیشن از اندروید 3 معرفی شده و با آن میتوان هر چیزی را در صفحه طبق روش های تعریف شده انیمیت کرد . بطور مثال متداول ترین روش های انیمیت در زیر معرفی شده .alpha ( محو شدگی )  rotation, rotationX, rotationY  ( چرخش و یا برگرداندن )  scaleX, scaleY ( بزرگنمایی یا کوچک کردن )  x, y, z ( جابجایی )  translationX, translationY, translationZ (API 21+) ( موقعیت دهی ) همانطور که می بینید این نوع انیمیشن روی روش های انیمیت کردن متمرکز شده و برایش فرقی ندارد که المان مورد نظر از چه نوعی میباشد و با آن میتوان به راحتی انیمیشن های گسترده ای ایجاد کرد . در زیر به انواع کلاس های طراحی شده برای اینکار خواهیم پرداختکلاس ObjectAnimator:با استفاده از کلاس ObjectAnimator می توان خصوصیات مختلف یک view را انیمیت کرد . متد های طراحی شده در این کلاس view مورد نظر و روش انیمیت و مقدار نهایی را دریافت کرده و خودش بقیه کار را به عهده میگرد .ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(tvLabel, &#34;alpha&#34;, 0.2f);&#xA;fadeAnim.start();در کد بالا tvLabel یک view برای انیمیت کردن و رشته ی &#34;alpha&#34; با استفاده از reflection خصوصیت مورد نظر  برای انیمیت کردن را پیدا کرده و آن را تا مقدار 0.2f  انیمیت خواهد کرد . البته میتوان به جای این کار از properties system in 4.0 استفاده کرد تا سرعت اجرا نیز بیشتر شود . زیرا اینکار دیگر نیاز به جستجوی خصوصیات در reflection نخواهد داشت . ObjectAnimator fadeAltAnim = ObjectAnimator.ofFloat(image, View.ALPHA, 0, 1);&#xA;fadeAltAnim.start();در ObjectAnimator میتوان از روش های انیمیت  شامل ALPHA, ROTATION, ROTATION_X, ROTATION_Y, SCALE_X, SCALE_Y, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, X, Y, Z استفاده کرد . همچینین میتوان مدت زمان و تکرار انیمیشن را نیز تعریف کرد .ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(tvLabel, &#34;scaleX&#34;, 1.0f, 2.0f);&#xA;scaleAnim.setDuration(3000);&#xA;scaleAnim.setRepeatCount(ValueAnimator.INFINITE);&#xA;scaleAnim.setRepeatMode(ValueAnimator.REVERSE);&#xA;scaleAnim.start();در ضمن AnimatorListenerAdapter رویداد های را در حین اجرای انیمیشن در اختیار ما قرار میدهد تا  بتوان کنترل درستی روی اجرای انیمیشن داشت .ObjectAnimator anim = ObjectAnimator.ofFloat(v, &#34;alpha&#34;, 0.2f);&#xA;anim.addListener(new AnimatorListenerAdapter() {&#xA;    @Override&#xA;    public void (Animator animation) {&#xA;        Toast.makeText(MainActivity.this, &#34;End!&#34;, Toast.LENGTH_SHORT).show();&#xA;    }&#xA;});&#xA;anim.start();گاهی اوقات نیاز است که چند انیمیت با هم اجرا شوند. کلاس AnimatorSet برای اینکار طراحی شده و با تعریف ObjectAnimator ها آنها را با هم اجرا میکند :AnimatorSet set = new AnimatorSet();&#xA;set.playTogether(&#xA;   ObjectAnimator.ofFloat(tvLabel, &#34;scaleX&#34;, 1.0f, 2.0f)&#xA;       .setDuration(2000),&#xA;    ObjectAnimator.ofFloat(tvLabel, &#34;scaleY&#34;, 1.0f, 2.0f)&#xA;        .setDuration(2000),&#xA;    ObjectAnimator.ofObject(tvLabel, &#34;backgroundColor&#34;, new ArgbEvaluator(),&#xA;          /*Red*/0xFFFF8080, /*Blue*/0xFF8080FF)&#xA;        .setDuration(2000)&#xA;);&#xA;set.start(); کلاس ViewPropertyAnimatorاین انیمیشن که بالاتر از ObjectAnimator قرار خواهد گرفت ، انیمیشن های سریعتری را اجرا خواهد کرد میتوان با آن خصوصیاتی را تغییر داد بدون استفاده از Support Libraryدر صورت استفاده نکردن از Support Library میتوان بصورت مستقیم از آن استفاده کرد Button btnExample = (Button) findViewById(R.id.btnExample);&#xA;btnExample.animate().alpha(0.2f).xBy(-100).yBy(100);با این انیمیشن هم میتوان خصوصیاتی زیادی را بصورت موازی تغییر داد و زمان اجرای انیمیشن و رویداد های آن را تغییر داده و انمیشن های پیچیده نوشت btnExample.animate().alpha(0.5f).rotation(90f).&#xA;  scaleX(2).xBy(100).yBy(100).setDuration(1000).setStartDelay(10).&#xA;  setListener(new AnimatorListenerAdapter() {&#xA;      @Override&#xA;      public void (Animator animation) {&#xA;          Toast.makeText(MainActivity.this, &#34;Started...&#34;, Toast.LENGTH_SHORT).show();&#xA;      };&#xA;  });با استفاده از Support Libraryزمانی که ازSupport Library استفاده میکنید و در گرادل کد زیر را اضافه کردید:implementation &#39;com.android.support:support-v4:24.2.0&#39;باید به این شکل از انیمیشن استفاده کنید تا در اندروید با API 11 نیز قابل اجرا باشد :Button btnExample = (Button) findViewById(R.id.btnExample);&#xA;ViewCompat.animate(btnExample).alpha(0.2f).xBy(-100).yBy(100);میتوان انیمیشن ها را در فایل های xml نیز تعریف کرد . که در مسیر res/animator قرار خواهد گرفت . و فایل آن بصورت زیر خواهد بود &lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;&#xA;&lt;set xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;    android:ordering=&#34;together&#34; &gt;&#xA;    &lt;objectAnimator&#xA;        android:propertyName=&#34;alpha&#34;&#xA;        android:valueTo=&#34;0.5&#34; &gt;&#xA;    &lt;/objectAnimator&gt;&#xA;    &lt;objectAnimator&#xA;        android:propertyName=&#34;rotation&#34;&#xA;        android:valueTo=&#34;90.0&#34; &gt;&#xA;    &lt;/objectAnimator&gt;&#xA;    &lt;objectAnimator&#xA;        android:propertyName=&#34;scaleX&#34;&#xA;        android:valueTo=&#34;2.0&#34; &gt;&#xA;    &lt;/objectAnimator&gt;&#xA;    &lt;objectAnimator&#xA;        android:propertyName=&#34;translationX&#34;&#xA;        android:valueTo=&#34;100.0&#34; &gt;&#xA;    &lt;/objectAnimator&gt;&#xA;    &lt;objectAnimator&#xA;        android:propertyName=&#34;translationY&#34;&#xA;        android:valueTo=&#34;100.0&#34; &gt;&#xA;    &lt;/objectAnimator&gt;&#xA;&lt;/set&gt;و اینطوری از آن استفاده خواهد شد Animator anim = AnimatorInflater.loadAnimator(this, R.animator.multi_button);&#xA;anim.setTarget(tvBlah);&#xA;anim.setDuration(1000);&#xA;anim.setStartDelay(10);&#xA;anim.addListener(new AnimatorListenerAdapter() {&#xA;     @Override&#xA;     public void (Animator animation) {&#xA;          Toast.makeText(MainActivity.this, &#34;Started...&#34;, Toast.LENGTH_SHORT).show();&#xA;     };&#xA;});&#xA;anim.start();کلاس ValueAnimatorبرخی از اوقات بجای تغییر خصوصیت یک view نیاز است که روی روند اجرای انیمیشن کنترل داشته باشیم . برای این شرایط ValueAnimator گزینه بسیار خوبی است .ValueAnimator valueAnim = ValueAnimator.ofFloat(0, 1);&#xA;valueAnim.setDuration(700);&#xA;valueAnim.setInterpolator(new DecelerateInterpolator());&#xA;valueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {&#xA;  @Override&#xA;    public void onAnimationUpdate(ValueAnimator animation) {&#xA;     float animatedValue = (float) animation.getAnimatedValue();&#xA;  }&#xA;});&#xA;if (!valueAnim.isStarted()) {&#xA;  valueAnim.start();&#xA;}انیمیشن های View Animationsاین نوع انیمیشن قبل از Property Animations معرفی شده بود و سیستمی کند تر دارد . معمولا نسبت به آن کمتر کاربرد دارد .با استفاده از Xmlفایل انیمیشن در مسیر res/anim قرار خواهد گرفت و معمولا بصورت زیر ساخته میشود :&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;&#xA;&lt;set xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;    android:fillAfter=&#34;true&#34; &gt;&#xA;    &lt;alpha&#xA;        android:duration=&#34;1000&#34;&#xA;        android:interpolator=&#34;@android:anim/accelerate_interpolator&#34;&#xA;        android:fromAlpha=&#34;1.0&#34;&#xA;        android:toAlpha=&#34;0.0&#34; /&gt;&#xA;&lt;/set&gt;حال بصورت زیر از ان استفاده خواهیم کرد :Animation animFadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out);  &#xA;animFadeOut.setAnimationListener(new AnimationListener() {&#xA;    @Override&#xA;    public void (Animation animation) {&#xA;        // Fires when animation starts&#xA;    }&#xA;    @Override&#xA;    public void (Animation animation) {&#xA;       // ...&#x9;&#x9;&#x9;&#xA;    }&#xA;    @Override&#xA;    public void onAnimationRepeat(Animation animation) {&#xA;       // ...&#x9;&#x9;&#x9;&#xA;    }&#xA;});&#xA;// start the animation&#xA;btnExample.startAnimation(animFadeOut);انتقال اکتیویتی Activity Transitionsهمانند انیمیشنی که برای view ها اجرا کردیم میتوان برای زمان انتقال اکتیویتی نیز انیمیشن اجرا کرد و اینکار معمولا در زمان ساختن intent انجام میگیرد :Intent i = new Intent(MainActivity.this, SecondActivity.class);&#xA;startActivity(i);&#xA;overridePendingTransition(R.anim.right_in, R.anim.left_out);پارامتر اول انیمیشن ورود به صفحه و پارامتر دوم انیمیشن خروج از صفحه می باشد که بصورت فایل xml تعریف شده است :&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;  &#xA;&lt;set xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;     android:interpolator=&#34;@android:anim/linear_interpolator&#34;&gt;  &#xA;  &lt;translate &#xA;      android:fromXDelta=&#34;100%p&#34; &#xA;      android:toXDelta=&#34;0&#34; &#xA;      android:duration=&#34;500&#34;/&gt;&#xA;&lt;/set&gt; و برای اینکه بتوان موقع برگشت به صفحه ( زدن دکمه back ) نیز انیمیشن بصورت برعکس اجرا شود باید فایل های xml جدا و بصورت برعکس را ایجاد کرده و در رویداد مورد نظر آن را صدا زد     @Override&#xA;    public void onBackPressed() {&#xA;        finish();&#xA;        overridePendingTransition(R.anim.left_in, R.anim.right_out);&#xA;    }انتقال اکتیویتی Activity Transitions در اندروید 5 به بعد کلاس Transitions API معرفی شده در اندروید 5  انیمیشن انتقال اکتیویتی را راحتر کرده ولی امکان استفاده از ان در ورژن های قبل از اندروید 5 وجود ندارد .  برای اینکار کافی است در مسیر res/transition فایل های xml با تگ های slide, fade, explode, autoTransition,  و recolor ایجاد کنیم  &lt;slide xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;    xmlns:app=&#34;http://schemas.android.com/apk/res-auto&#34;&#xA;    android:slideEdge=&#34;right&#34;&#xA;    android:duration=&#34;1000&#34;/&gt;و به این صورت از ان استفاده کنیم :public class MainActivity extends AppCompatActivity {&#xA;    @Override&#xA;    protected void onCreate(Bundle savedInstanceState) {&#xA;        Transition transition =TransitionInflater.from(this).inflateTransition(R.transition.slide_right);        &#xA;        getWindow().setExitTransition(transition);کد بالا  برای انیمیشن خروج اکتیویتی می باشد که در جای خود تعریف شده و در واقع  4 حالت میتوان انیمیشن تعریف کرد ، ورود / خروج - بازگشت / ورود مجدد . برای تعریف انیمیشن ورود به صفحه باید هنگام تعریف intent  آن را تعریف کرد Intent i = new Intent(MainActivity.this, SecondActivity.class);&#xA;ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);&#xA;startActivity(i, options.toBundle());در صورتی که انیمیشن های بازگشت / ورود مجدد تعریف نشوند ، همان انمیشن های ورود و خروج بصورت برعکس اجرا خواهد شد ولی اگر بخواهید انیمیشنی برای بازگشت اجرا شود باید بجای متد finish  از متد  finishAfterTransition استفاده کنید . برای دیدن نمونه های مختلف استفاده از این نوع انیمیشن به اینجا و اینجا و اینجا و اینجا و اینجا و اینجا  سر بزنید تغییر انیمیشن پیش فرض انتقال اکتیویتیمیتوان انیمیشن پیش فرض در مسیر res/styles.xml را در theme نیز تغییر داد . &lt;style name=&#34;AppTheme&#34; parent=&#34;AppBaseTheme&#34;&gt;&#xA;    &lt;item name=&#34;android:windowAnimationStyle&#34;&gt;@style/CustomAnimation.Activity&lt;/item&gt;&#xA;&lt;/style&gt;&#xA;&lt;style name=&#34;CustomAnimation.Activity&#34; parent=&#34;@android:style/Animation.Activity&#34;&gt; &#xA;    &lt;item name=&#34;android:activityOpenEnterAnimation&#34;&gt;@anim/slide_in_right&lt;/item&gt;&#xA;    &lt;item name=&#34;android:activityOpenExitAnimation&#34;&gt;@anim/slide_out_left&lt;/item&gt;&#xA;    &lt;item name=&#34;android:activityCloseEnterAnimation&#34;&gt;@android:anim/slide_in_left&lt;/item&gt;&#xA;    &lt;item name=&#34;android:activityCloseExitAnimation&#34;&gt;@android:anim/slide_out_right&lt;/item&gt;&#xA;&lt;/style&gt;انیمیشن Fragment Transitionsفرگمنت ها را نیز همانند اکتیویتی میتوان هنگام انتقال انیمیت کرد . برای اینکار کافی ایست مثل اکتیویتی در مسیر res/anim فایل xml ایجاد کرد &lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;&#xA;&lt;set xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&gt;&#xA;    &lt;translate android:fromXDelta=&#34;-50%p&#34; android:toXDelta=&#34;0&#34;&#xA;        android:duration=&#34;@android:integer/config_mediumAnimTime&#34;/&gt;&#xA;    &lt;alpha android:fromAlpha=&#34;0.0&#34; android:toAlpha=&#34;1.0&#34;&#xA;        android:duration=&#34;@android:integer/config_mediumAnimTime&#34; /&gt;&#xA;&lt;/set&gt;و سپس در هنگام ایجاد فرگمنت با استفاده از setCustomAnimations آنها را صدا زد FragmentTransaction fts = getSupportFragmentManager().beginTransaction();&#xA;// Configure the &#34;in&#34; and &#34;out&#34; animation files&#xA;fts.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);&#xA;// Perform the fragment replacement&#xA;ft.replace(R.id.fragment_container, newFragment, &#34;fragment&#34;);&#xA;// Start the animated transition.&#xA;ft.commit();در ضمن اندروید یکسری انیمیشن های از پیش تعریف شده را در مسیر  R.anim  دارد که میتوان مستقیم به آن دسترسی داشته و در انیمیشن استفاده کرد . برای خواندن مطالب بیشتر به اینجا مراجعه کنید FragmentTransaction fts = getSupportFragmentManager().beginTransaction();&#xA;fts.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right);&#xA;fts.replace(R.id.fragment_container, newFragment, &#34;fragment&#34;);&#xA;fts.commit();انیمیشن های لی اوت Layout Animationsانیمیشن شروع کنندهمی توان برای هر layout انیمیشنی را تعریف کرد که هنگام ظاهر شدن در صفحه اجرا شود . برای اینکار کافی است انیمیشن مورد نظر را در فایل xml در مسیر res/anim ایجاد کنید &lt;set xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;  &#xA;      android:interpolator=&#34;@android:anim/accelerate_interpolator&#34;&gt;&#xA;    &lt;translate android:fromXDelta=&#34;-100%p&#34; android:toXDelta=&#34;0&#34;&#xA;            android:duration=&#34;1000&#34; /&gt; &#xA;&lt;/set&gt;و سپس یک فایل xml دیگر با تگ  layoutAnimation میسازیم و انیمیشن مورد نظر را ادرس دهی میکنیم &lt;layoutAnimation xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;        android:delay=&#34;30%&#34;&#xA;        android:animationOrder=&#34;reverse&#34;&#xA;        android:animation=&#34;@anim/slide_right&#34;&#xA;/&gt;حال در android:layoutAnimation در فایل لی اوت آن را قرار میدهیم &lt;LinearLayout &#xA;    ...&#xA;    android:layoutAnimation=&#34;@anim/layout_bottom_to_top_slide&#34; /&gt;به همین سادگی هنگام شروع برنامه تمام view هایی که در این لی اوت قرار دارند زمان ظاهر شدن در صفحه با انیمیت خواهند شدمتریال انیمیشن Material Animationsدر اندروید 5 امکانات مختلفی برای انیمیشن طبق زبان طراحی متریال معرفی شده که کار انیمیت کردن زیبا تر می کند المان اشتراکی در انیمیشن انتقال اکتیویتی - Shared Element Activity Transition : در این انیمیشن با معرفی یک المان مشترک در دو اکتیویتی میتوان انیمیشنی ساخت که در هر دو اکتیویتی برای همان المان اجرا شود بصورت ادامه دار اجرا شود .انیمیشن موجی - Ripple Animation :این انیمیشن با ایجاد افکتی مثل موج پس از لمس المان ایجاد میشودانیمیشن ظاهر شونده - Circular Reveal Animationاین انیمیشن یک انیمیت ظاهر شونده دایره ای است که بیشتر در float button اعمال میشودانیمیشن سلسه مراتبی - View Hierarchy Animationsدر این انیمیشن با تعریف مراحل بصورت Scene کنترلی  روی اجرای انیمیشن خواهیم داشتدر پایان چند کتابخانه مهم برای انیمیشن در اندروید را معرفی میکنم :ViewAnimator - AndroidViewAnimations - RecyclerView Animators - ListViewAnimations - NineOldAndroids - Leonids - Like Button</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Tue, 03 Aug 2021 14:25:05 +0430</pubDate></item><item><title>Jetpack Compose</title><link>https://virgool.ir/@morteza.darzi/jetpack-compose-z75ddz351xnd</link><description>حدود دو سال پیش در رویداد گوگل Google I/O 2019 بود که 6 فریمورک جدید از Android Jetpack معرفی شد و البته همشون اون موقع ورژن آلفا بودند . یکی از اون ها که خیلی دل برنامه نویس های اندروید رو برده بود Jetpack Compose بود . روشی جدید برای طراحی رابط کاربری در اندروید . از همون موقع مشخص شد که هدف گذاری گوگل سمت و سو دادن برنامه نویسان به نوشتن رابط کاربری اعلانی - declarative  هست و یه فرضیه هم میگه گوگل با استفاده از flutter که از همین روش استفاده میکنه خواسته برنامه نویسان رو آماده کنه برای رفتن به این سمت و گرفتن بازخورد و بعدش در یه همچین روزایی محصول کار رو بچینه !پس از 5 ماه که ورژن بتا این فریمورک اومده بود ، بالاخره دو روز پیش 28 July 2021 ( چهارشنبه 6 مرداد 1400 ) خبر استیبل شده ورژن 1.0 این کتابخانه تو وبلاگ گوگل اومد . یعنی میتونیم با کد های کاتلین هم رابط کاربری بسازیم از همون روش اعلانی و این بار همه چیز کاملا در اختیار شماست اما واقعا چه خبره مگه ؟ گوگل مدعی است :ما برای ساخت سریعتر و آسان تر رابط های چند سکویی ( Native ) فریمورک Compose را طراحی کردیم. با یک رویکرد کاملاً اعلانی ( declarative ) ، شما فقط رابط کاربری خود را توصیف می کنید ، و Compose باقی ماجرا را انجام خواهد داد . با تغییر وضعیت برنامه ، رابط کاربری شما به طور خودکار به روز می شود ورژن 1.0 آماده استفاده میباشد و ویژگی های زیر را خواهد داشت :قابل همکاری : فریمورک Compose برای ایجاد تعامل با برنامه موجود شما ساخته شده است. می توانید رابط های تعریف شده Compose را در Views یا Views را در Compose قرار دهید. می توانید به اندازه فقط یک دکمه به صفحه اضافه کنید ، یا نمای سفارشی خود را که در Compose ایجاد کردید را در صفحه نمایش دهید.ادغام پذیری با Jetpack: فریمورک Compose برای ادغام با کتابخانه های Jetpack که از قبل می شناسید و دوستشان دارید ساخته شده است. مثلا با Navigation ، Paging ، LiveData (یا Flow)طراحی متریال : Compose پیاده سازی Material Design را فراهم میکند ، ساخت برنامه های زیبا که نشان دهنده نام تجاری شماست را آسان می کند. درک و ردیابی سیستم Material بدون نیاز به کار با چندین فایل XML آسان تر است.لیست ها : مولفه های Compose’s Lazy یک روش ساده ، مختصر اما قدرتمند برای نمایش کارآمد لیست داده ها ، با حداقل کد فراهم میکند ( یه جورایی داره میگه از RecyclerView و Adapter هاش دیگه خبری نیست )انیمیشن: رابط انیمیشن Compose برای ساخت رابط های زیبا و کاربر پسند به کمک شما خواهد آمد خوب دیگه چی از گوگل میخواید برید بگیرید نصب کنید لذتش رو ببرید . قبلش باید برید به سایت اندروید و Android Studio Arctic Fox که نسخه جدید سازگار شده با Compose هست رو نصب کنید ( همین الان دانلودش برای من تموم شد - حدود 900 مگ ) . یکی از قابلیت های فوق العاده همراه این فریمورک Compose Preview که میتونید در لحظه نتیجه رابط کاربری که طراحی کردید رو ببینید ، یه چیزی شبیه همون Hot Reload تو فلاتر . اگر فرصتی بشه سعی میکنم همینطور که دارم شیرجه میزنم تو آموزش های این فریمورک ، چیزایی رو که  یاد گرفتم هم تو ویرگول بنویسم </description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Fri, 30 Jul 2021 20:57:25 +0430</pubDate></item><item><title>خلاصه کتاب آموزش اندروید - WorkManager</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-آموزش-اندروید-workmanager-cyint9irbxvm</link><description>این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسمدر برنامه های تجاری بطور معمول نیاز به فرایند های دوره ای می باشد ، مثلا  هر بار چک میشود که اگر اطلاعات جدیدی ایجاد شده ، به کاربر اعلام گردد .در این مطلب سعی خواهیم کرد نحوه پیاده سازی این کار را توضیح دهیم .معمولا باید این کارهای دوره ای در پس زمینه انجام شود تا حتی زمانی که کاربر از برنامه استفاده نمیکند هم بتوان آن را اجرا کرد . فرض کنید میخواهیم هر یک ساعت چک کنیم اگر عکس جدیدی به دیتابیس سرور اضافه شده به کاربر اطلاع دهیم .با استفاده از WorkManager که از کتابخانه های Jetpack می باشد ، یک کلاس Worker خواهیم ساخت که چک کردن وجود عکس جدید را در آن تعریف خواهیم کرد و سپس آن را برای اجرا در دوره ی زمانی خاص برنامه ریزی خواهیم کرد .در صورتی که عکس جدیدی وجود داشته در Notification به کاربر اطلاع خواهیم داد .ابتدا dependency زیر به gradle برنامه اضافه کنید :implementation &#34;android.arch.work:work-runtime:last_version&#34;ساخت کلاس Worker :کلاس اجرایی را با ارث بری از Worker خواهیم ساخت . متد doWork برای درخواست هایی است که میخواهیم در پس زمینه اجرا شود و نوع برگشتی آن از نوع Result می باشد ، که Result.success یعنی کار به درستی انجام شده و Result.failure یعنی اجرا کامل انجام نشده و اجرای درخواست دیگر ادامه نخواهد شد و Result.retry هم یعنی مشکلی در زمان اجرا به وجود امده و شما میخواهید که در درخواست بعدی دوباره سعی شود .class PollWorker(val context: Context, workerParameters: WorkerParameters)&#xA;: Worker(context, workerParameters) {&#xA;       override fun doWork(): Result {&#xA;        Log.i(TAG, &#34;Work request triggered&#34;)&#xA;        return Result.success()&#xA;    }&#xA;}زمانبندی اجرا  :جهت زمانبندی اجرای دستور از WorkRequest استفاده خواهیم کرد . خود کلاس WorkRequest از نوع abstract می باشد و مستقیم از ان ارث بری کرد ولی کلاس های فرزندی دارد که بسته به نوع زمانبندی از آنها ارث بری خواهیم کرد . مثلا اگر میخواهید درخواست چک کردن یکبار اجرا شود از OneTimeWorkRequest و اگر میخواهیم بصورت دوره ای چک شود از PeriodicWorkRequest ارث بری میکنیم : class PhotoGalleryFragment : Fragment() {&#xA;    ...&#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;        ...&#xA;        lifecycle.addObserver(thumbnailDownloader.fragmentLifecycleObserver)&#xA;&#xA;        val workRequest = OneTimeWorkRequest&#xA;            .Builder(PollWorker::class.java)&#xA;            .build()        &#xA;         WorkManager.getInstance().enqueue(workRequest)&#xA;    }سپس وقتی که درخواست مثل مثال بالا ساخته شد با استفاده از کلاس WorkManager و گرفتن یک وهله از ان با استفاده از getInstance ، درخواست را جهت اجرا به متد enqueue می فرستیم . با اینکار درخواست مورد نظر در دوره مشخص شده اجرا خواهد شد .شرط اجرا درخواست :در ضمن میتوان برای اجرا شدن درخواست ، یک سری شروط نیز تعیین کرد . مثلا در صورت وجود اینترنت شرط اجرا شود یا در صورتی که دستگاه در حال شارژ می باشد . اینکار با کلاس Constraints و تعریف شروط اجرا و اتصال ان به درخواست با setConstraints قابل انجام میباشد .val constraints = Constraints.Builder()&#xA;            .setRequiredNetworkType(NetworkType.UNMETERED)&#xA;            .build()&#xA;        val workRequest = OneTimeWorkRequest&#xA;            .Builder(PollWorker::class.java)&#xA;            .setConstraints(constraints)&#xA;            .build()&#xA;        WorkManager.getInstance().enqueue(workRequest)پیروز باشید </description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Mon, 17 May 2021 19:36:42 +0430</pubDate></item><item><title>خلاصه کتاب آموزش اندروید - Retrofit  ارتباط با سرور 2</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-آموزش-اندروید-retrofit-ارتباط-با-سرور-2-u2kxmrlxsje0</link><description>این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسمقسمت اول در سری اول این مطلب در خصوص پیاده سازی ارتباط با سرور با استفاده از Retrofit به تفصیل صحبت شد و در این مطلب سعی خواهم کرد نحوه استفاده درست از Retrofit در  اپ تجاری را توضیح دهمارتباط با سرور زمان تغییر تنظیمات دستگاه :اگر طبق مثال مطلب قبل اپ را ساخته باشید و هنگام ارتباط به سرور دستگاه را چند بار بچرخوانید خواهید دید هر بار درخواست جدیدی به سرور ارسال می شود و این بدلیل استفاده از Retrofit در متد onCreate می باشد . در برنامه های تجاری باید وقتی که فرگمنت ساخته میشود ، درخواست به سرور ارسال شده و نتیجه در حافظه نگهداری شود تا هر بار که به آن نتیجه احتیاج بود از آن استفاده شود و با چرخیدن و دوباره سازی فرگمنت نیز اطلاعات از بین نرود .راه حل استفاده از Viewmodel است . متد های ارسال درخواست در Viewmodel قرار دارد و در زمان ساخته شدن آن ، درخواست به سرور ارسال شده و به فرگمنت متصل خواهد شد و با دوباره سازی فرگمنت اطلاعات در Viewmodel باقی میماند و قابل استفاده است . البته در صورتی که هنگام ارسال درخواست به سرور ، فرگمنت و اکتیویتی از بین بروند نتیجه نیز از بین میرود ، اما در اپلیکیشن های تجاری ممکن است این نتایج در دیتابیس یا حافظه های داخلی ذخیره میشود تا در دفعه بعد از آن استفاده شود . همچنین می توان با استفاده از LiveData در Viewmodel نتایج را به محض اماده شدن در رابط کاربری نمایش دهید .اینکار را باید در متد onViewCreated انجام دهید تا مطمئن شوید که view به طور کامل ساخته شده و همچنین مطمئن میشوید که وقتی فرگمنت دوباره سازی میشود این متد دوباره صدا زده میشود و با استفاده از LiveData  اطلاعات دوباره به نمایش در می اید .لغو درخواست :ممکن است طراحی برنامه به شکلی باشد که با از بین رفتن Viewmodel نیاز به نگهداری نتایج نباشد . میتوان با cancel کردن شی Call درخواست را لغو کنیم و عملا به برنامه خواهیم گفت نیاز به برگرداندن نتیجه نیست . اینکار با صدا زدن Call.cancel انجام میشود و بعد از آن Callback.onFailure اجرا خواهد شد که میتوان در آن متد ، با چک کردن Call.isCancelled مطمئن شد که درخواست لغو شده یا خطایی در سرور رخ داده است . در ضمن باید اینکار را در ViewModel.onCleared قرار داد تا هر وقت Viewmodel میخواهد از بین برود ( مثل زمانی که کاربر دکمه back  را میزند و از اکتیویتی خارج میشود ) درخواست نیز کنسل شود .مدیریت وابستگی ها :یکی از الگو های طراحی Dependency injection است که در ان باید وابستگی بین اشیا به کمترین مقدار برسد . به بیان ساده تر ، بجای ساختن و صدا زدن یک شی در یک کلاس و وابسته شدن آن کلاس به آن شی ، آن را از طریق constractor دریافت کنیم . FlickrFetchr(flickrApi: FlickrApi) {  &#xA;fun fetchContents(): LiveData&lt;String&gt; {  ...  }  در حال حاضر پیشنهاد گوگل استفاده از کتابخانه Dagger 2 برای اجرای این الگوی طراحی در اندروید می باشد .صفحه بندی نتایج :به صورت پیش فرض تمام اطلاعات دریافتی از سرور بصورت کامل برگردانده می شود و این نیازمند صرف وقت زیادی است . با استفاده از کتابخانه Paging در Jetpack می توان اطلاعات را صفحه بندی کرده و در صورتی که مثلا کاربر اسکرول میکند اطلاعات صفحه ها را از سرور درخواست کرد .</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Wed, 05 May 2021 22:01:35 +0430</pubDate></item><item><title>خلاصه کتاب آموزش اندروید - Retrofit  ارتباط با سرور 1</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-آموزش-اندروید-retrofit-ارتباط-با-سرور-1-fwzysnyclanc</link><description>این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسمدر این مطلب نحوه استفاده از Retrofit برای ایجاد ارتباط با سرور را توضیح میدهیم و خواهیم گفت چطور با استفاده از کتابخانه Gson پاسخ سرور را از JSON به کلاس های کاتلین تبدیل کنید . Retrofit با استفاده از متد های اینترفیسی که تعریف میکنیم کلاس های مورد نیاز برای ارتباط را ساخته و بصورت یک درخواست HTTP آن را ارسال میکند و پاسخی از نوع HTTP را به فرمت OkHttp.ResponseBody برمی گرداند . اما بهتر است که اطلاعات دریافتی به شکل نوع داده اپلیکیشن شما برگردانده شود که برای این منظور Retrofit  با استفاده از تبدیل کننده ها ( converter ) داده های شما را به درخواست تبدیل کرده و پاسخ ها را نیز به شکلی که میخواهید بر میگرداند . اولین قدم مثل همیشه افزودن dependency است :implementation &#39;com.squareup.retrofit2:retrofit:last_version&#39;رابط API که باید تعریف شود همان interface کاتلین می باشد که با annotations تزئین شده :interface FlickrApi {&#xA; @GET(&#34;/&#34;)&#xA; fun fetchContents(): Call&lt;String&gt;&#xA; }هر متد باید با یکی از انواع درخواست های HTTP مشخص شود . انواع متداول GET, POST, PUT, DELETE می باشد . هر متد تعریف شده یک آدرس نیز دارد که ادامه دهنده آدرس URL اصلی است که از قبل تعریف خواهد شد ، اما  &#34; / &#34; در این متد نشان میدهد که همان آدرس اصلی به عنوان مقصد درخواست خواهد بود . نوع Call  نیز جهت برگرداندن پاسخ از سرور می باشد که با تعیین &lt;Call&lt;String به Retrofit خواهد گفت پاسخ سرور را به String برگرداند . حال تنها باید retrofit را بسازیم :val retrofit: Retrofit = Retrofit.Builder()&#xA; .baseUrl(&#34;https://www.flickr.com/&#34;)&#xA; .build()&#xA;&#xA; val flickrApi: FlickrApi = retrofit.create(FlickrApi::class.java)&#xA; }&#xA; با تعریف Retrofit.Builder و اعلام یک آدرس URL اولیه کار تمام است . فقط حتما باید حواستان باشد که در آخر آدرس URL یک &#34;/&#34; نیز اضافه کنید تا مطمئن شوید که آدرس تعریف شده در API به ادامه این آدرس ترکیب شود . همانطور که مشخص است Retrofit هیچ کلاسی را در زمان کامپایل نمی سازد و تنها وقتی در زمان اجرا کد retrofit.create اجرا میشود کلاس های مورد نظر با استفاده از رابط API که تعریف کرده اید ساخته میشود . برا اینکه بتوان پاسخ سرور را به نوع String تبدیل کرد باید از تبدیل کننده استففاده کرد که اینکار با تبدیل کننده ای که توسط تیم Retrofit ساخته شده به سادگی کد زیر قابل انجام است . فقط باید از قبل dependency مورد نظر را اضافه کنید :implementation &#39;com.squareup.retrofit2:converter-scalars:last_version&#39;حال با کد زیر به سادگی پاسخ دریافتی از سرور قبل از برگرداندن نتیجه به نوع String تبدیل خواهد شد:val retrofit: Retrofit = Retrofit.Builder()&#xA; .baseUrl(&#34;https://www.flickr.com/&#34;)&#xA; .addConverterFactory(ScalarsConverterFactory.create())&#xA; .build()&#xA; &#xA; val flickrApi: FlickrApi = retrofit.create(FlickrApi::class.java)&#xA; }جهت اجرای درخواست و دریافت نتیجه باید به شکل زیر درخواست رابط API را با استفاده از متد enqueue در onCreate اجرا کنیم :val flickrHomePageRequest: Call&lt;String&gt; = flickrApi.fetchContents()flickrHomePageRequest.enqueue(object : Callback&lt;String&gt; {&#xA;     override fun onFailure(call: Call&lt;String&gt;, t: Throwable) {&#xA;             Log.e(TAG, &#34;Failed to fetch photos&#34;, t)&#xA;      }&#xA; &#xA;      override fun onResponse(call: Call&lt;String&gt;, response: Response&lt;String&gt; ) {&#xA;             Log.d(TAG, &#34;Response received: ${response.body()}&#34;)&#xA;     }&#xA; })متد Call.enqueue دو کار بسیار عالی را برای ما انجام میدهد ، اول اینکه ارسال درخواست را طبق قوانین ترد اندروید در ترد پس زمینه اجرا میکند و دوم اینکه پاسخ دریافتی از سرور را در ترد اصلی برای به روز رسانی رابط کاربری بر میگرداند ،در ضمن متد enqueue جهت اجرا بصورت صف می باشد ، یعنی میتوانید چندین درخواست را با هم به آن اعلام کرده و Retrofit بصورت یکی یکی درخواست را ارسال و پاسخ را دریافت خواهد کرد . البته شما میتوانید بصورت مستقیم با استفاده از Call.execute هم درخواست را ارسال کنید فقط باید اجرا در ترد پس زمینه را بصورت دستی اعمال کنید .آخرین کار نیز افزودن کد دسترسی استفاده از اینترنت به فایل AndroidManifest می باشد : &lt;uses-permission android:name=&#34;android.permission.INTERNET&#34; /&gt;قبلا در خصوص مزیت ها استفاده از LiveData در مطالب قبلی صحبت کردیم ، اینجا نیز با استفاده از قدرت فوق العاده این کتابخانه می توان کد تمیزی را ایجاد کرد . برای اینکار باید تنظیمات Retrofit را به یک کلاس جدا انتقال داده و نوع برگشتی را نیز از نوع LiveData تعریف کنیم :fun fetchContents(): LiveData&lt;String&gt; {&#xA;     val responseLiveData: MutableLiveData&lt;String&gt; = MutableLiveData()&#xA;     val flickrRequest: Call&lt;String&gt; = flickrApi.fetchContents()&#xA; &#xA;     flickrRequest.enqueue(object : Callback&lt;String&gt; {&#xA;         override fun onFailure(call: Call&lt;String&gt;, t: Throwable) {&#xA;             Log.e(TAG, &#34;Failed to fetch photos&#34;, t) &#xA;         }&#xA;        override fun onResponse(call: Call&lt;String&gt;,response: Response&lt;String&gt;) {&#xA;            Log.d(TAG, &#34;Response received&#34;)&#xA;            responseLiveData.value = response.body()&#xA;         }&#xA;     })&#xA;     return responseLiveData&#xA;     }&#xA; }حالا به راحتی میتوان در برنامه نتیجه درخواست از سرور را observe کرده و هر وقت که نتیجه برگردانده شد رابط کاربری را به روز رسانی کنیم : val flickrLiveData: LiveData&lt;String&gt; = FlickrFetchr().fetchContents()&#xA;flickrLiveData.observe(this,Observer { &#xA;        responseString -&gt;Log.d(TAG, &#34;Response received: $responseString&#34;)&#xA; })بطور معمول اطلاعات دریافتی از سرور از نوع JSON می باشد که ما باید بتوانیم آن را به مدل برنامه خود تبدیل کنیم تا به راحتی قابل استفاده باشد . مثلا لیستی از تصاویر و اطلاعات آن ، برای اینکار ابتدا متد جدیدی در رابط API تعریف میکنیم :interface FlickrApi {&#xA;.......&#xA;     @GET(&#xA;                  &#34;services/rest/?method=flickr.interestingness.getList&#34; +&#xA;                  &#34;&amp;api_key=yourApiKeyHere&#34; +&#xA;                  &#34;&amp;format=json&#34; +&#xA;                  &#34;&amp;nojsoncallback=1&#34; +&#xA;                  &#34;&amp;extras=url_s&#34; )&#xA;    fun fetchPhotos(): Call&lt;String&gt;&#xA;    }همانطور که میبینید در آدرس درخواست یکسری اطلاعات مثل api_key نیز ارسال میشود که بسته به سرور شما  اطلاعات مختلفی نیاز است .سپس طبق ساختار JSON دریافتی از سرور یک data class می سازیم :data class GalleryItem(&#xA;        var title: String = &#34;&#34;,&#xA;        var id: String = &#34;&#34;,&#xA;        var url: String = &#34;&#34;&#xA; )برای تبدیل باید از کتابخانه Gson استفاده کنیم که dependency زیر را اضافه میکنیم :implementation &#39;com.google.code.gson:gson:last_version&#39;&#xA;implementation &#39;com.squareup.retrofit2:converter-gson:last_version&#39;سپس کلاس های مدل براساس اطلاعات دریافتی JSON را می سازیم و با استفاده از SerializedName ارتباط بین فیلد های مدل و پاسخ را بصورت صریح مشخص میکنیم :data class GalleryItem(&#xA;        var title: String = &#34;&#34;,  &#xA;        var id: String = &#34;&#34;,  &#xA;        @SerializedName(&#34;url_s&#34;) &#xA;        var url: String = &#34;&#34;  &#xA;)&#xA;&#xA;class PhotoResponse {  &#xA;      @SerializedName(&#34;photo&#34;)  &#xA;       lateinit var galleryItems: List&lt;GalleryItem&gt;&#xA;}سپس کلاسی برای نوع دریافتی پاسخ ایجاد میکنیم :class FlickrResponse {  &#xA;           lateinit var photos: PhotoResponse  &#xA;}حال کافی است در کلاس های ساخته شده برای ارتباط با سرور بجای دریافت String از مدل های بالا استفاده کنیم . البته با معرفی Gson به عنوان تبدیل کننده :interface FlickrApi {&#xA;       @GET(...)&#xA;       fun fetchPhotos(): Call&lt;FlickrResponse&gt;&#xA; }val retrofit: Retrofit = Retrofit.Builder()&#xA; .baseUrl(&#34;https://api.flickr.com/&#34;)&#xA; .addConverterFactory(GsonConverterFactory.create())&#xA; .build()&#xA; &#xA; flickrApi = retrofit.create(FlickrApi::class.java)&#xA; }&#xA; &#xA; fun fetchPhotos(): LiveData&lt;List&lt;GalleryItem&gt;&gt; {&#xA;              val responseLiveData: MutableLiveData&lt;List&lt;GalleryItem&gt;&gt; = MutableLiveData()&#xA;              val flickrRequest: Call&lt;FlickrResponse&gt; = flickrApi.fetchPhotos()&#xA; &#xA;              flickrRequest.enqueue(object : Callback&lt;FlickrResponse&gt; {&#xA;                         override fun onFailure(call: Call&lt;FlickrResponse&gt;, t: Throwable) {&#xA;                                       Log.e(TAG, &#34;Failed to fetch photos&#34;, t) &#xA;                          }&#xA; &#xA;                           override fun onResponse(call: Call&lt;FlickrResponse&gt;,response: Response&lt;FlickrResponse&gt;) {&#xA;                                       Log.d(TAG, &#34;Response received&#34;)&#xA;                                       responseLiveData.value = response.body()&#xA;                                       val flickrResponse: FlickrResponse? = response.body()&#xA;                                       val photoResponse: PhotoResponse? = flickrResponse?.photos&#xA;                                       var galleryItems: List&lt;GalleryItem&gt; = photoResponse?.galleryItems?: mutableListOf()&#xA;                                        galleryItems = galleryItems.filterNot {&#xA;                                                          it.url.isBlank()&#xA;                                         }&#xA;                                          responseLiveData.value = galleryItems&#xA;                                 }&#xA;                        })&#xA;                   return responseLiveData&#xA;           }&#xA; }ادامه در قسمت دوم ....</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Wed, 28 Apr 2021 18:42:52 +0430</pubDate></item><item><title>خلاصه کتاب آموزش اندروید - implicit intent</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-آموزش-اندروید-implicit-intent-dn88ocn7lpcy</link><description>این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسماجرای برنامه های اندروید با جابجایی بین اکتیویتی ها انجام میشود و به راحتی میتوان با ساخت intent و معرفی نام اکتیویتی و متد  startActivity به ان اکتیویتی رفته و کارها را انجام داد . اما ممکن است زمانی فقط نیاز به انجام کاری باشد و فرقی نمیکند در کدام اکتیویتی یا برنامه انجام میشود و بسته به برنامه هایی که کاربر روی دستگاه خود دارد انتخاب هایش متفاوت می باشد . یک مثال بسیار متداول اشتراک گذاری ( share ) است . در هر برنامه یک منو اشتراک گذاری وجود دارد که با زدن ان ، گزینه هایی از برنامه های که دارید نشان داده میشود و میتوانید یک برنامه را انتخاب کنید . به این روش implicit intent میگویند ، یعنی اجرای غیر صریح اکتیویتی .در اجرای غیر صریح به سیستم گفته میشود که نیاز به انجام چه کاری دارید و سیستم یک اکتیویتی را در اپلیکیشن مرتبط اجرا خواهد کرد .حال اگر اکتیویتی های بیشتری وجود داشته باشد که میتوانند ان کار را انجام دهند سیستم گزینه هایی را برای انتخاب ارائه خواهد کرد . برای اجرای غیر صریح اکتیویتی باید مثل زیر عمل کنیم :Intent(Intent.ACTION_SEND).apply {&#xA;                type = &#34;text/plain&#34;&#xA;                putExtra(Intent.EXTRA_TEXT, &#34; Message &#34;)&#xA;                putExtra(Intent.EXTRA_SUBJECT, &#34; Subject &#34;)&#xA;            }.also { intent -&gt;&#xA;                startActivity(intent)&#xA;            }&#xA;        }در اینجا ابتدا از طریق سازنده ( constructor ) کلاس Intent ، نوع کار( action ) مورد نظر را از طریق یکسری string را که جزو ثابت های ( constant ) تعریف شده در Intent می باشد می سازیم . این پارامتر به سیستم خواهد گفت که قصد چه کاری را داریم . نام این پارامتر ها با ACTION شروع میشود و میتوان نمونه های زیادی از آن را در گوگل جستجو کرد .علاوه بر تعریف نوع کاری که میخواهیم انجام دهیم میتوان اطلاعاتی را نیز به اکتیویتی مورد نظر فرستاد که بسته به نوع action این اطلاعات دریافتی متفاوت می باشد ، در مثال بالا اینکار با  putExtra انجام گرفت و همانطور که می بینید در اینجا نیز از ثابت های تعریف شده در Intent برای تشخیص نوع اطلاعات ارسالی استفاده میشود و با اینکار اکتیویتی هدف می فهمد که چه اطلاعاتی را دریافت کرده است.</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sun, 25 Apr 2021 00:37:48 +0430</pubDate></item><item><title>خلاصه کتاب اموزش اندروید - MVVM + DataBinding + LiveData</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-اموزش-اندروید-mvvm-databinding-livedata-jwdns7kf4532</link><description>این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسممعماری MVVM :نوشتن برنامه ها براساس معماری یعنی رعایت یکسری اصول تعریف شده در آن برای ایجاد نظم بیشتر . یکی از معماری های عالی برای برنامه های اندروید MVVM می باشد که شامل بخش های Model , View , Viewmodel می باشد . در برنامه های اندرویدی View همان فایل های layout ، اکتیویتی و فرگمنت ها می باشد و Model نیز کلاس های منطق برنامه است . اما Viewmodel کلاسی است که ارتباط دهنده بین View و Model میباشد . این Viewmodel با ویومدلی که در کامپوننت Jetpack قرار دارد از نظر معنایی متفاوت بوده ، هر چند میتواند از هر دو استفاده کرد . ویومدل Jetpack برای کار با اطلاعات در چرخه حیات فرگمنت و اکتیویتی است ، ولی Viewmodel یک قسمت انتزاعی از معماری MVVM می باشد که کار اتباط دهی را انجام می دهد و میتواند از ویومدل Jetpack نیز ارث بری کند .نحوه ارتباط بین Viewmodel و View در معماری بطوری تعریف شده که Viewmodel  به هیچ وجه نباید به View وابسته باشد ، برای اینکار باید از طریق DataBinding بین آنها ارتباط برقرار کرد ، بطوری که View با استفاده از DataBinding بصورت غیر مستقیم ازتغییرات Viewmodel با خبر می شود . برای فعال کردن DataBinding به شکل زیر ابتدا dependency را اضافه میکنیم :apply plugin: &#39;kotlin-kapt&#39;&#xA;android {&#xA;    ...&#xA;    buildTypes {&#xA;        ...&#xA;    }&#xA;    dataBinding {&#xA;        enabled = true&#xA;    }&#xA;}سپس در هر فایل layout که میخواهیم DataBinding را در آن اجرا کنیم ، کل تگ های آن را با تگ &lt;layout&gt; می پوشانیم ، مثل کد زیر : &lt;layout xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&gt;&#xA;    &lt;androidx.recyclerview.widget.RecyclerView&#xA;            xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;            android:id=&#34;@+id/recycler_view&#34;&#xA;            android:layout_width=&#34;match_parent&#34;&#xA;            android:layout_height=&#34;match_parent&#34;/&gt;&#xA;&lt;/layout&gt;حال اگر یکبار پروژه را build کنید ، اندروید استودیو بصورت اتوماتیک یک سری کلاس به فرم CamelCase با نام فایل layout و با پسوند Binding می سازد تا در DataBinding بتوان از آن استفاده کرد . مثلا برای فایل activity_main فایلی با نام ActivityMainBinding ساخته خواهد شد که در اکتیویتی به شکل زیر استفاده خواهد شد :class MainActivity : AppCompatActivity() {&#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;&#xA;        super.onCreate(savedInstanceState)&#xA;&#xA;        val binding: ActivityMainBinding =&#xA;            DataBindingUtil.setContentView(this, R.layout.activity_main)&#xA;    }&#xA;}اولین کاری که باید با DataBinding انجام داد معرفی layout در ابتدای برنامه است که اینکار با DataBindingUtil.setContentView انجام می شود . حال زمان تعریف Viewmodel است ، کلاسی که اطلاعات Model را برای نمایش در View  آماده میکند و نحوه نمایش منطق برنامه را از خود نمایش جدا میکند . کافی است یک کلاس ساخته و منطق برنامه را در آن لود کنیم :class ViewModel {&#xA;    var sound: Sound? = null&#xA;        set(sound) {&#xA;            field = sound&#xA;        }&#xA;&#xA;    val title: String?&#xA;        get() = sound?.name&#xA;  }حال برای اینکه بتوان از Viewmodel در View استفاده کرد با استفاده از تگ &lt;data&gt; و تعریف  &lt;variable&gt;  عملا Viewmodel را در View تعریف میکنیم :&lt;layout xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;        xmlns:tools=&#34;http://schemas.android.com/tools&#34;&gt;&#xA;    &lt;data&gt;&#xA;        &lt;variable&#xA;            name=&#34;viewModel&#34;&#xA;            type=&#34;com.bignerdranch.android.beatbox.SoundViewModel&#34;/&gt;&#xA;    &lt;/data&gt;&#xA;    ...&#xA;&lt;/layout&gt;حال بطور مثال برای نمایش اطلاعات روی android:text یک Button بجای تعریف مستقیم از پیشوند  @ با property که در ViewModel تعریف شده ارتباط را بقرار میکنیم :&lt;Button&#xA;        ....&#xA;        android:text=&#34;@{viewModel.title}&#34;&#xA;       /&gt;و آخرین کار ایجاد کلاس Viewmodel و چسباندن آن به binding در اکتیویتی می باشد : binding.viewModel = SoundViewModel()درست است که Viewmodel به View بطور کامل متصل شده و اصول MVVM نیز رعایت شده ، ولی هر بار که بخواهیم اطلاعات را جهت نمایش اماده کنیم باید در Viewmodel تغییری در Model ایجاد کنیم و به View اطلاع بدهیم که اطلاعات تغییر کرده . برای اینکار روش های مانند Observable وجود دارد ولی یکی از بهترین راه حل ها برای اینکار استفاده از  LiveData برای property هایی است که در DataBinding شرکت میکنند به این شکل :class SoundViewModel  {&#xA;        val title: MutableLiveData&lt;String?&gt; = MutableLiveData()&#xA;        var sound: Sound? = null&#xA;            set(sound) {&#xA;                field = sound&#xA;                title.postValue(sound?.name)&#xA;            }&#xA;        val title: String?&#xA;            get() = sound?.name&#xA;    }اینطوری LiveData به صورت اتوماتیک اطلاعات تغییر یافته را به DataBinding  اطلاع خواهد داد ، در نتیجه  بصورت اتوماتیک  View به روز خواهد شد . تنها کار باقی مانده اتصال چرخه حیات برای DataBinding است که مثل LiveData هر وقت که فرگمنت یا اکتیویتی از بین رفت DataBinding  نیز از بین برود ، به شکل زیر می توان اکتیویتی را به عنوان LifecycleOwner معرفی کرد :binding.lifecycleOwner = this@MainActivityبه همین سادگی و تمام . فقط یک نکته بسیار عالی اینکه اگر بخواهید میتوانید حتی به صورت مستقیم رویداد هایی مثل  در Button را هم در Viewmodel صدا بزنیم ، بدون اینکه کدی در فرگمنت یا اکتیویتی نوشته شود :&lt;Button&#xA;    .....&#xA;    android:=&#34;@{() -&gt; viewModel.onButtonClicked()}&#34; &#xA;/&gt;</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sat, 24 Apr 2021 18:58:54 +0430</pubDate></item><item><title>خلاصه کتاب اموزش اندروید - Room + LiveData</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-اموزش-اندروید-room-livedata-wa4xzbl75wf5</link><description>Android Programming: The Big Nerd Ranch Guideاین سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسم ذخیره سازی اطلاعات در برنامه های اندروید بوسیله کامپوننت Room بر پایه دیتابیس sqllite میباشد . این کامپوننت با استفاده از annotation هایی که برای کلاس ها تعریف میکنیم بصورت خودکار کلاس های ارتباط با دیتابیس را میسازد . برای کار با Room  باید کلاس های خاصی ساخته شود که در زیر به انها میپردازیمقبل از هر چیز باید dependency به build.gradle بصورت زیر اضافه شود :apply plugin: &#39;kotlin-kapt&#39;implementation &#34;androidx.room:room-runtime:$room_version&#34;&#xA;kapt &#34;androidx.room:room-compiler:$room_version&#34;استفاده از kapt به اندروید استودیو اجازه میدهد به کلاس های خودکار ساخته شده دسترسی داشته و بتوانیم از انها در برنامه استفاده کنیم . room-runtime شامل کتابخانه هایی برای ساختن کلاس های ارتباط با دیتابیس و room-compiler شامل کتابخانه هایی برای ساخت کلاس ها مورد نیاز از طریق annotation ها بصورت خودکار میباشد . حالا کافی است یکبار پروژه را sync کنید کلاس های مدل - Entity :هر یک از table های دیتابیس باید بصورت کلاس هایی با Entity@ تعریف شود ، بطور مثال :@Entity&#xA;data class Crime(&#xA;                @PrimaryKey (autoGenerate =true)&#xA;                 val id: UUID ,&#xA;                 var title: String = &#34;&#34;,&#xA;                 var date: Date = Date(),&#xA;                 var isSolved: Boolean = false)کلاس دیتابیس - Database :کلاس دیتابیس همانطور که از نامش پیداست معرف دیتابیس مورد استفاده میباشد . این کلاس نیز با Database@ تعریف میشود و دارای پارامتر هایی نیز میباشد ، پارامتر های اولیه شامل کلاس های entity که  تعریف شده و ورژن دیتابیس برای شناسایی تغییرات احتمالی بعدی دیتابیس میباشد . بطور مثال :@Database(entities = [ Crime::class ], version=1)&#xA;abstract class CrimeDatabase : RoomDatabase() {&#xA;}کلاس های تبدیل داده - TypeConverterاغلب نوع اطلاعاتی که در دیتابیس ذخیره میشود با نوع مورد نیاز برنامه متفاوت است ، مثلا در برنامه به نوع داده Date نیاز داریم ولی دیتابیس از ذخیره سازی این نوع پشتیبانی نمیکند ، راه حل تبدیل نوع میباشد که میتوان به راحتی با متد های با TypeConverter@ تعریف شده و معرفی شده در Database اینکار را در Room انجام میدهیم ، بطور مثال :class CrimeTypeConverters {&#xA;    @TypeConverter&#xA;    fun fromDate(date: Date?): Long? {&#xA;        return date?.time&#xA;    }&#xA;    @TypeConverter&#xA;    fun toDate(millisSinceEpoch: Long?): Date? {&#xA;        return millisSinceEpoch?.let {&#xA;            Date(it)&#xA;        }&#xA;    }و به این صورت در دیتابیس ان را اعلام میکنیم :@Database(entities = [ Crime::class ], version=1)&#xA;@TypeConverters(CrimeTypeConverters::class)&#xA;abstract class CrimeDatabase : RoomDatabase() {&#xA;}رابط دسترسی به داده - DAO :برای اینکه بتوان با دیتابیس و table های ان ارتباط برقرار کرد ، اینترفیسی با Dao@ تعریف میکنیم که با استفاده از annotation یا دستورات صریح sqllite با دیتابیس تراکنش خواهد داشت :@Dao&#xA;interface CrimeDao {&#xA;    @Query(&#34;SELECT * FROM crime&#xA;    fun getCrimes(): List&lt;Crime&gt;&#xA;&#xA;    @Query(&#34;SELECT * FROM crime WHERE id=(:id)&#34;)&#xA;    fun getCrime(id: UUID): Crime?&#xA;}برای استفاده از dao یک متد abstract که مقدار برگشت ان از نوع dao است در Database میسازیم :@Database(entities = [ Crime::class ], version=1)&#xA;@TypeConverters(CrimeTypeConverters::class)&#xA;abstract class CrimeDatabase : RoomDatabase() {&#xA;       abstract fun crimeDao(): CrimeDao&#xA;}کلاس مخزن - Repositoryگوگل توصیه میکند برای ارتباط با دیتابیس از الگوی Repository استفاده کنیم ، این الگو دسترسی به دیتابیس را کپسوله کرده و به عنوان لایه بالاتر از دیتابیس میتوان برای گرفتن اطلاعات از وب سرویس یا منابع دیگر نیز استفاده کرد . برای مثال :class CrimeRepository private constructor(context: Context) {&#xA;&#xA;     companion object {&#xA;        private var INSTANCE: CrimeRepository? = null&#xA;         fun initialize(context: Context) {&#xA;             if (INSTANCE == null) {&#xA;                 INSTANCE = CrimeRepository(context)&#xA;             }&#xA;        }&#xA;fun get(): CrimeRepository {&#xA;            return INSTANCE ?:&#xA;            throw IllegalStateException(&#34;CrimeRepository must be initialized&#34;)&#xA;        }&#xA;    }&#xA;}همانطور که از کد بالا مشخص است Repository با الگوی singletone پیاده سازی شده ، این الگو تضمین میکند فقط یک مخزن از داده در برنامه وجود داشته و هر بار که از طریق get ساخته میشود همان مخزن میباشد (چون constructor ان بصورت private تعریف شده نمیتوان نمونه جدیدی از روی ان ساخت )اما اینکار نیازمند ساخته شدن Repository در ابتدای اجرای برنامه توسط اندروید میباشد و اینکار باید در Application انجام شود ، یعنی کلاسی میسازیم که از Application ارث بری کرده و ان را در متد oncreate مربوطه intialaize میکنیم : class CriminalIntentApplication : Application() {&#xA;    override fun onCreate() {&#xA;        super.onCreate()&#xA;        CrimeRepository.initialize(this)&#xA;    }&#xA;}در ضمن باید حتما کلاس تعریف شده را در manifest نیز تعریف کنیم :&lt;manifest&gt;&#xA; xmlns:android=&#34;http://schemas.android.com/apk/res/android&#34;&#xA;         package=&#34;com.bignerdranch.android.criminalintent&#34;&gt;&lt;application&#xA;            android:name=&#34;.CriminalIntentApplication&#34;&#xA;            android:allowBackup=&#34;true&#34;&#xA;            ... &gt;&#xA;        ...&#xA;    &lt;/application&gt;&lt;/manifest&gt;حال کافی است در Repository دیتابیس را بسازیم و به ان دسترسی داشته باشیم :private const val DATABASE_NAME = &#34;crime-database&#34;&#xA;class CrimeRepository private constructor(context: Context) {&#xA;    private val database : CrimeDatabase = Room.databaseBuilder(&#xA;        context.applicationContext,&#xA;        CrimeDatabase::class.java,&#xA;        DATABASE_NAME&#xA;    ).build()&#xA;&#xA;    private val crimeDao = database.crimeDao()    fun getCrimes(): List&lt;Crime&gt; = crimeDao.getCrimes()    fun getCrime(id: UUID): Crime? = crimeDao.getCrime(id)همانطور که میبینید برای ساخت دیتابیس به context و کلاس database و نام دیتابیس نیاز میباشد . حال به راحتی به dao دیتابیس دسترسی داشته و با ان ارتباط برقرار میکنیم. و کافی است در Viewmodel از Repository استفاده کنیم :class CrimeListViewModel : ViewModel() {&#xA;    val crimes = mutableListOf&lt;Crime&gt;()&#xA;    init {&#xA;        for (i in 0 until 100) {&#xA;            val crime = Crime()&#xA;            crime.title = &#34;Crime #$i&#34;&#xA;            crime.isSolved = i % 2 == 0&#xA;            crimes += crime&#xA;        }&#xA;    }&#xA;    private val crimeRepository = CrimeRepository.get()&#xA;    val crimes = crimeRepository.getCrimes()&#xA;}حالا وقتی برنامه را اجرا کنیم با خطای زیر مواجه خواهیم شد :java.lang.IllegalStateException: Cannot access database on the main thread since    it may potentially lock the UI for a long period of time.این خطا بدلیل اجرای کد Room در ترد اصلی برنامه است ، جایی که UI در ان اجرا میشود و به صورت پیش فرض اندروید اجازه اجرای کارهای زمان بر را در این ترد نمیدهد . راه حل های مختلفی برای رفع این ایرور وجود دارد . یکی از آنها استفاده از LiveData است .LiveData یک نگهدارنده ( holder ) اطلاعات می باشد که به خوبی با Room هماهنگ می باشد و همچنین قابلیت انتقال اطلاعات بین ترد های مختلف را دارد .قبل از هر چیز باید dependency زیر اضافه شود :implementation &#34;androidx.lifecycle: lifecycle-extension:$last_version&#34;زمانی که از طریق DAO اطلاعات دیتابیس بصورت LiveData برگردانده میشود ، اجرای برنامه روی ترد پس زمینه اجرا شده و پس از اتمام اجرا نتایج روی آن انتقال پیدا میکند . برای اینکار DAO را به شکل زیر تغییر میدهیم :interface CrimeDao {&#xA;&#xA;  @Query(&#34;SELECT * FROM crime&#34;)&#xA;  fun getCrimes(): LiveData&lt;List&lt;Crime&gt;&gt;&#xA;&#xA;  @Query(&#34;SELECT * FROM crime WHERE id=(:id)&#34;)&#xA;  fun getCrime(id: UUID): LiveData&lt;Crime?&gt;&#xA;}و همچنین باید کد Repository را نیز به شکل زیر تغییر دهیم :class CrimeRepository private constructor(context: Context) {&#xA;  ...&#xA;  private val crimeDao = database.crimeDao()&#xA;&#xA;  fun getCrimes(): LiveData&lt;List&lt;Crime&gt;&gt; = crimeDao.getCrimes()&#xA;&#xA;  fun getCrime(id: UUID): LiveData&lt;Crime?&gt; = crimeDao.getCrime(id)&#xA;  ...&#xA;}و اما اصل ماجرا . LiveData از الگوی Observer استفاده میکند و باید برای استفاده از نتایج برگدانده توسط آن یک Observer برای آن تعریف کرد تا عملیاتی را که نیاز است بعد از تکمیل نتایج انجام شود در آن انجام داد . بطور معمول پس از دریافت نتایج نیاز به نمایش اطلاعات روی View برنامه می باشد ، برای همین از متد onCreateView در fragment استفاده میکنیم ، به صورت زیر :override fun onViewCreated(view: View, savedInstanceState: Bundle?) {&#xA;        super.onViewCreated(view, savedInstanceState)&#xA;&#xA;        crimeListViewModel.crimeListLiveData.observe(&#xA;            viewLifecycleOwner,&#xA;            Observer { crimes -&gt;&#xA;                crimes?.let {&#xA;                    Log.i(TAG, &#34;Got crimes ${crimes.size}&#34;)&#xA;                    updateUI(crimes)&#xA;                }&#xA;        })&#xA;    }متد observe سه پارامتر دریافت میکند . پارامتر اول viewLifecycleOwner همان جزئی از برنامه است که چرخه حیات این LiveData به آن وابسته است ( fragment ) و در صورتی که چرخه حیات این بخش پایان یابد LiveData نیز به کار خود پایان خواهد داد و از بین خواهد رفت . چون اگر قبل از برگرداندن نتایج LiveData فرگمنت مورد نظر از بین برود و LiveData بخواهد اطلاعات را روی View نمایش دهد با ایرور مواجه شده و برنامه از کار می افتد ، برای همین موضوع است که LiveData را یک lifecycle aware یعنی وابسته به چرخه حیات معرفی کرده اند .دومین پارامتر نیز متدی است که هنگام برگردانده شدن اطلاعات باید اجرا شود و بوسیله ان به راحتی میتوان view برنامه را آپدیت کرد . حال اگر برنامه را اجرا کنیم میبینیم که اطلاعات به راحتی از دیتابیس دریافت شده و در برنامه به نمایش در آمده است .نکات مهم :1. دریافت اطلاعات از دیتابیس زمانبر بوده و ب باسته به حجم اطلاعات ممکن است از چند میلی ثانیه تا چند ثانیه طول بکشد .برای همین بسته به طراحی برنامه باید بر نحوه نمایش تغییر اطلاعات به کاربر پس از دریافت اطلاعات حتما پروسه ای تعیین گردد . مثلا استفاده از Loader یا دریافت اطلاعات از قبل در حافظه برنامه نمایش آن پس از دریافت کامل و از این دست کارها . یکی از روش هایی که برای جلوگیری از تغییر وضعیت برخی از المان های صفحه بصورت انیمیشنی اجرا میشود استفاده از () View.jumpDrawablesToCurrentState است که جلو اجرای انیمیشن تغییر وضعیت مثلا CheckBox را پس از دریافت اطلاعات گرفته و مستقیما آن را نمایش میدهد :checkBox.apply {&#xA;            isChecked = crime.isSolved&#xA;            jumpDrawablesToCurrentState()&#xA;        }2. اغلب ممکن است دریافت اطلاعات از دیتابیس نیازمند دریافت اطلاعات دیگر قبل از ان از دیتابیس باشد ، مثلا ابتدا اطلاعات یک محصول با استفاده از id از دیتابیس گرفته میشود و سپس نیاز است مقدار فروش ان محصول از دیتابیس دریافت شود . در صورتی از LiveData استفاده شود هر دو نوع برگشت داده شده از دیتابیس از همین نوع است و دریافت نتیجه نیازمند دریافت اولین LiveData میباشد . برای اینکار میتوان از متد های transformation در LiveData استفاده کرد که دو نوع را بهم مرتبط میکند .</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Fri, 16 Apr 2021 15:44:06 +0430</pubDate></item><item><title>Android Navigation Component</title><link>https://virgool.ir/@morteza.darzi/android-navigation-component-udeydk3vqso6</link><description>گوگل در androidx کامپوننتی را معرفی کرده که بسیار در پیاده سازی ارتباط صفحات رابط کاربری ، کاربرد دارد . کامپوننت Navigation سعی خواهد کرد با ارائه ابزار هایی نحوه ارتباط و پیمایش بین صفحات و انتقال اطلاعات بین انها را کنترل کند . توصیه گوگل نیز استفاده از معماری single activity در زمان استفاده از این کامپوننت است . یعنی یک اکتیویتی با چندین فرگمنت که صفحات مختلف برنامه را خواهد ساخت . در این مطلب بصورت پایه سعی میکنم نحوه استفاده از Navigation را توضیح بدهم .قبل از هر چیز باید  dependency را اضافه کنیدbuildscript {ext.nav_version = &#39;2.3.4&#39;}implementation &#34;androidx.navigation:navigation-fragment-ktx:${nav_version}&#34;اولین قدم تعریف گراف پیمایش است ، جایی که مسیر ها تعریف میشود ، برای اینکار روی پوشه res راست کلیک کرده و بصورت زیر فایل nav_graph را اضافه میکنیم :با کلیک روی nav_graph صفحه ادیتور باز خواهد شد که میتوان فرگمنت های برنامه را به عنوان destination اضافه کرد . همچنین با کشیدن از یک فرگمنت به فرگمنت دیگر یک action ایجاد خواهد شد که همان مسیر حرکت بین فرگمنت هاست . با استفاده از id که برای action تعریف میشود میتوان بعدا دستور رفتن به صفحه بعد را صادر کرد  :میتوانید کد قسمت nav_graph  را بعد از افزودن action به صورت زیر ببینید . همانطور که مشخص است این گراف نیز از یک فایل xml تشکیل شده که تمام مشخصات گفته شده در ان وجود دارد در مرحله بعد باید فضایی در اکتیویتی تعریف شود که محل بکار گیری گراف و نمایش فرگمنت ها در ان است .این مکان با نام nav_host به صورت زیر به layout اکتیویتی اضافه خواهد شد :&lt;fragment&#xA;    android:id=&#34;@+id/nav_host_fragment&#34;&#xA;    android:name=&#34;androidx.navigation.fragment.NavHostFragment&#34;&#xA;    android:layout_width=&#34;match_parent&#34;&#xA;    android:layout_height=&#34;match_parent&#34;&#xA;    app:defaultNavHost=&#34;true&#34;&#xA;    app:navGraph=&#34;@navigation/nav_graph&#34; /&gt;حال برای جابجایی بین فرگمنت ها کافی است از nav_controler بصورت زیر در فرگمنت مبدا استفاده کرد . در کد با استفاده از متد navigate و id همان action  که قبلا تعریف کردیم به راحتی به فرگمنت بعدی پیمایش خواهیم کرد :findNavController().navigate(R.id.action_mainFragment_to_listFragment)بطور معمول علاوه بر پیمایش بین فرگمنت ها نیاز به تبادل اطلاعات بین انها نیز میباشد ، بطور مثال کاربر روی یک کالا کلیک کرده و در صفحه بعد طبق شناسه کالا جزئیات ان نمایش داده خواهد شد . گوگل برای اینکار توصیه میکند از SafeArgs استفاده کنید. برای اینکار ابتدا در قسمت build.gradle مربوط به module کد زیر را اضافه میکنیم :dependencies {&#xA;    classpath &#34;androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version&#34;&#xA;}سپس در build.gradle مربوط به app کد زیر را اضافه خواهیم کرد. حتما پس از اینکار یکبار پروژه را clean و سپس rebuild کرده تا اندروید استودیو کلاس های مورد نیاز را بصورت خودکار بسازد :plugins {&#xA;    id &#39;androidx.navigation.safeargs.kotlin&#39;&#xA;}قبل از شروع ابتدا باید در nav_graph برای فرگمنتی که دریافت کننده اطلاعات است در قسمت Argument یک متغییر را تعریف کنید سپس در فرگمنت ارسال کننده ، با استفاده از کلاس ساخته شده توسط SafeArgs که از نام فرگمنت + Directions  تشکیل شده و متد هایی با نام action که از قبل ساخته این اطلاعات مورد نظر را بصورت پارامتر می فرستیم val action = MainFragmentDirections.actionMainFragmentToListFragment(count)&#xA;findNavController().navigate(action)در فرگمنت دریافت کننده نیز به سادگی با استفاده کلاس ساخته شده که ترکیب نام فرگمنت +Args تشکیل شده استفاده کرده و بصورت زیر اطلاعات را دریافت کرده و استفاده خواهیم کرد val count = ListFragmentArgs.fromBundle(requireArguments()).listCountامیدارم مفید واقع شود</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sun, 11 Apr 2021 00:06:40 +0430</pubDate></item><item><title>خلاصه کتاب اموزش اندروید - Viewmodel</title><link>https://virgool.ir/@morteza.darzi/خلاصه-کتاب-اموزش-اندروید-viewmodel-ndtvuab6gx2y</link><description>این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide  که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسمViewmodel  :بطور معمول کاربرها انتظار دارند پس از چرخش صفحه موبایل یا مخفی شدن اکتیویتی و کلا حالت های چهارگانه چرخه حیات ، برنامه دوباره به همان وضعیت قبل برگردد ، اما پیاده سازی کامل چنین کاری در اکتیویتی ها پر هزینه خواهد بود . Viewmodel برای رفع همین مشکل ساخته شده ، یعنی جدا سازی منطق برنامه از اکتیویتی ، بطوری که وقتی اکتیویتی در چرخه حیات خود در وضعیت های مختلف قرار بگیرد ، Viewmodel همچنان در حافظه باقی است و هر وقت اکتیویتی دوباره سازی شود ، از همان Viewmodel استفاده کرده و اخرین اطلاعات قبل از وضعیت ترک اپلیکیشن به کاربر نمایش داده خواهد شدکار اصلی Viewmodel تزئین اطلاعات model یا همان منطق برنامه برای نمایش در اکتیویتی است .اولین قدم برای استفاده از Viewmodel قبل از هر چیز افزودن dependence به build.gradle : Impementation &#39;androidx.lifecycle:lifecycle-extensions:+latest-version&#39;حالا باید با استفاده از ViewModelProviders که وظیفه تامین Viewmodel را برای اکتیویتی بر عهده دارد ، یک نمونه ساخته و در کل اکتیویتی استفاده کنیم :class MainActivity : AppCompatActivity() {&#xA;    ...&#xA;    private val quizViewModel: QuizViewModel by lazy {  ViewModelProviders.of(this).get(QuizViewModel::class.java)&#xA;    }&#xA;override fun onCreate(savedInstanceState: Bundle?) {} با استفاده از lazy میتوان یکبار ViewModelProviders را تعریف کرده و بصورت val از ان در اکتیویتی استفاده کرد و همچنین تضمین خواهد کرد Viewmodel دقیقا یکبار تعریف و پیاده سازی شود ، همچنین اجازه استفاده از Viewmodel را قبل از Activity.onCreate(…) خواهد گرفت همانطور که در logcat برنامه میبینید یکبار فقط Viewmodel ساخته شده و در هر وضعیت اکتیویتی به هیچ وجه از بین نمیرود ، تا زمانی مثل logcat زیر اکتیویتی نیز destroy شود ، در اینصورت Viewmodel نیز از بین خواهد رفت نکته مهم :در حال حاضر نحوه intialize کردن Viewmodel در اکتویتی به صورتی که در بالا با استفاده از ViewModelProviders گفته شد دیگر deprecated شده و بهتر برای اینکار ابتدا dependence  زیر اضافه کنیم :implementation &#39;androidx.activity:activity-ktx:last-version&#39;بعد بصورت زیر Viewmodel را تعریف کنید :private val myViewmodel: MyViewmodel by viewModels()</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sun, 04 Apr 2021 17:18:13 +0430</pubDate></item><item><title>چرخه حیات اکتیویتی اندروید به زبان ساده - 2</title><link>https://virgool.ir/@morteza.darzi/چرخه-حیات-اکتیویتی-اندروید-به-زبان-ساده-2-bukqu0scyzmw</link><description>همونطور که در قسمت قبلی بصورت ساده توضیح داده شد هر کدوم از این متد ها در زمان خاصی از چرخه حیات اکتیویتی فراخوانی میشن . اما سوال اینجاست که چه کد هایی رو باید در این متد ها نوشت و چه موقع ازشون استفاده کرد  https://virgool.io/p/bukqu0scyzmw/%DA%86%D8%B1%D8%AE%D9%87%D8%AD%DB%8C%D8%A7%D8%AA%D8%A7%DA%A9%D8%AA%DB%8C%D9%88%DB%8C%D8%AA%DB%8C%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%D8%A8%D9%87%D8%B2%D8%A8%D8%A7%D9%86%D8%B3%D8%A7%D8%AF%D9%87%DA%86%D8%B1%D8%AE%D9%87%D8%AD%DB%8C%D8%A7%D8%AA%D8%A7%DA%A9%D8%AA%DB%8C%D9%88%DB%8C%D8%AA%DB%8C%D8%A8%D8%B5%D9%88%D8%B1%D8%AA%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86%DB%8C%D8%B3%D8%A7%D8%AF%D9%87%D8%AA%D9%88%D8%B6%DB%8C%D8%AD%D8%AF%D8%A7%D8%AF%D9%87%D8%B4%D8%AF%D9%87virgool.io برای درک بهتر موضوع باید بدانیم از چه وضعیتی وارد متد شده و بعد از آن به کجا خواهیم رفت . بریم ببینیم در هر متد چه کدی نوشته میشود :چرخه حیات اکتیویتیمتد onCreate :همانطور که گفته شد این گیت ورودی است وقتی که اکتیویتی ساخته میشود و همچنین وقتی از بین برود و دوباره ساخته شود این متد فراخوانی خواهد شد . از طرفی بعد از این متد هم  قرار دارد که در آن اپلیکیشن برای کاربر قابل نمایش است پس در این متد تمام نیاز های اولیه اکتیویتی باید ساخته شود . مثل کلاس های برای ارتباط با سرور ، کلاس های برای ارتباط با دیتابیس یا حتی مقدار دهی فیلد های داخلی و هر موضوع که در طول اکتیویتی به آن نیاز است .متد  :ورود به این متد از وضعیت اجرا است . اپلیکیشن در این حالت موقتا به پس زمینه میرود ، پس در این متد باید کارهایی که در حال انجام است ، نه کارهای اجرایی نمایش اکتیویتی متوقف شود. مثلا فرضا داشتیم در اخرین لحظه عکسی را لود میکردیم ، در این متد میتوان لود کردن عکس را متوقف کرد چون کاربر فعلا در حال انجام کار دیگری است . همچنین میتوان آخرین مقادیر بدست امده تا این لحظه را در این متد ذخیره کرد تا در برگشت دوباره قابل استفاده باشد . البته این بسته به نیاز برنامه دارد و ممکن است نیاز برنامه طوری تعریف شده باشد که با متوقف شدن اپ باز هم به کارش ادامه دهد .متد  :این متد دقیقا پس از وضعیت سکوت که در قسمت قبلی توضیح دادیم اتفاق می افتد و جایی است که میرویم تا اپلیکیشن را از کاربر مخفی کنیم . معمولا کارهایی که در حال انجام بوده است و هنوز تمام نشده در این متد متوقف میشود . مثلا دریافت اطلاعات از سرور یا محاسبات خاص . در ضمن می شود در این مرحله اطلاعات مهم مورد نیاز در زمان برگشت به برنامه را ذخیره کرد . تفاوت این متد با  این است که پس از این متد اپ دیگر به کاربر نمایش داده نمیشود و عملا برگشت کاربر نیاز به انتخاب او دارد . پس باید تقریبا اپ به حالت توقف با انتظار برگشت ادامه دهدمتد onRestart :این متد چیزی شبیه onCreate میباشد برای زمانی که از وضعیت خاموشی امده ایم . یعنی پیش زمینه کارهایی که در  متوقف شده باید در این متد ساخته شود تا در متد بعدی  استفاده شود . متد  :این متد از وضعیت خاموشی صدا زده میشود . یعنی قبلا در  کارهایی در متوقف شده است . باید ان کارها در این متد از سر گرفته شود . هر چند این متد بعد از onCreate هم صدا زده میشود ولی کار اصلی ان برگرداندن وضعیت به حالت اجرا از حالت خاموشی است . با این حال میتوان کارهای مرتبط به شروع اجرای اپ در این متد انجام شود با دید به این سمت که این کارهای مرتبط در  میتواند متوقف شود ، کارهایی که بتوان در زمان نمایش ندادن اپ به کاربر متوقف شود . میتوان چک کرد اگر اطلاعات ذخیره شده در  وجود دارد طبق آن پیاده سازی شود ، عملا این متد و  در دو سوی مختلف وضعیت خاموشی قرار دارند . متد  :این متد بعد از حالت سکوت صدا زده میشود . یعنی اپ موقتا متوقف شده بود . کاری اگر در حال انجام بوده و در  متوقف شده از اینجا باید از سر گرفته شود . اطلاعاتی اگر ذخیره شده میتوان استفاده شود . در ضمن بعد از  نیز این متد صدا زده میشود هرچند مقصود اصلی وجود این متد نیست ولی میتوان کارهای اولیه اپ را نیز در آن انجام داد با دید به اینکه این کار قبلا متوقف شده در  باشد . عملا این متد و  در دو سمت حالت سکوت قرار دارند متد onDestroy :همانطور که مشخص است این متد برای ازاد سازی منابع در اختیار گرفته شده اپ می باشد و قبل از توقف کامل اپ میتواند مورد استفاده قرار گیرد . متد onSaveInstanceState :این متد قبل از متد onDestroy یا حتی قبل از  اجرا خواهد شد و محلی است برای ذخیره سازی اطلاعاتی که وقتی اپ دوباره از onCreate ساخته خواهد شد استفاده شود . این اطلاعات در Bundle ذخیره خواهد شد و از طریق پارامتر متد onCreate(savedInstanceState: Bundle?) قابل دسترسی است . اینکار در یک فعالیت تو کار در سیستم اندروید انجام خواهد شد و معمولا بیشترین استفاده آن برای زمانی است که دستگاه موبایل حالت چرخش را ایجاد کند . در این حالت اپ به حالت های سکوت و خاموشی رفته و حتی onDestroy نیز فراخوانی خواهد شد و باز از اول onCreate  صدا زده میشود و میتوان مقادیری را از طریق پارامتر به متد پاس داد .نکته :از اندروید 7 به بالاتر این امکان اضافه شده که بتوان دو اپ را با هم در یک صفحه مشاهده کرد . باید این را در نظر بگیرید که زمانی که اکتیویتی شما برای این حالت آماده میشود در حالت سکوت قرار میگیرد و  فراخوانی میشود و وقتی هر دو اپ در صفحه قرار گرفتند  فراخوانی شده و اپ در حالت اجرا می باشد . حتی زمانی که در قسمت دیگر صفحه در حال کار با اپ دیگر هستید نیز اپ در حالت اجرا باقی می ماند . </description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Mon, 29 Mar 2021 20:18:07 +0430</pubDate></item><item><title>چرخه حیات اکتیویتی اندروید به زبان ساده - 1</title><link>https://virgool.ir/@morteza.darzi/چرخه-حیات-اکتیویتی-اندروید-به-زبان-ساده-ozcyyzukpscw</link><description>اکتیویتی به عنوان بدنه اصلی برنامه های اندروید در زمان اجرا میتونه در وضعیت های مختلفی قرار بگیره . این نقاط وضعیتی و حرکت بین این وضعیت ها به عنوان چرخه حیات اکتیویتی شناخته میشه . سعی میکنم با مثال ساده ای مفهوم این چرخه حیات رو برسونم . فرض میکنیم اکتیویتی یک خواننده است که دعوت شده برامون بخونه ، هر وضعیت هم یک اتاق و برای رفتن به هر اتاق هم اون خواننده باید از گیت هایی رد بشه و چیزایی رو بده یا بگیره . برای اینکه اکتیویتی بره بخونه باید بره اتاق اجرا اتاق اجرابرای رفتن به این اتاق باید به ترتیب از گیت هایی به نام (3)onCreate(1) , (2) ,  بگذره .حالا چرا این همه گیت ؟ خوب یه گیت onCreate میزاشتید تمام . بهتون میگم مسیر رفتن اکتیویتی به اتاق اجرافرض کنید تو هر گیت بهش یه چیز میدیم مثلا تو  onCreate &#34; لباس خوانندگی &#34; ، تو  &#34; ساز موسیقی &#34; و تو  هم &#34; دفتر نت موسیقی &#34; ، فعلا در همین حد . اکتیویتی وقتی رفت تو اتاق میتونه بخونه . در واقع اینجا همون صفحه موبایلتونه که داره اکتیوتی توش اجرا میشه و باهاتون تعامل میکنه . وسط خوندن اکتیویتی کار موقتی برامون پیش میاد مثلا سیستم اندروید میاد بالا از ما میپرسه اجازه داره به چیزی دست بزنه ( permission ) . اینجاست که اکتیویتی موقتا میره به یه اتاق دیگه تا ما به کارمون برسیم ، برای خروج از این اتاق هم یه راه بیشتر نداریم ، فقط میشه رفت اتاق بعدی که &#34; اتاق سکوت &#34;.اتاق سکوتبرای اینکه از اتاق اجرا بیایم اتاق سکوت باید از گیت  بگذریم . تو این گیت ازش &#34; دفتر نت موسیقی &#34; رو میگیرن تا نتونه ساز بزنه . هر چند ما بازم اکتیویتی رو میبینیم ولی اون نمیتونه برامون بخونه . مسیر رفتن از اتاق اجرا به اتاق سکوتاز اتاق سکوت میشه برگشت اتاق اجرا گیتش هم همون گیت  که گفتیم کارش پس دادن &#34; دفتر نت موسیقی &#34; به اکتیویتی بوده به این سادگی اکتیویتی دوباره میره اتاق اجرا و میخونه . مسیر برگشت از اتاق سکوت به اتاق اجراحالا چه وسط اجرای اکتیویتی و چه زمانی که تو &#34; اتاق سکوت &#34; هست ، ممکنه این وسط گوشیمون زنگ بخوره ، اجبارا چون دیگه با اکتیویتی کاری نداریم ، باید بره اتاق دیگه به نام &#34; اتاق خاموشی &#34;اتاق خاموشیگیت ورودی به این اتاق که از اتاق سکوت میاد  هست اینجا از اکتیویتی سازش رو میگیرن .به این اتاق ما دید نداریم و اکتیویتی رو نمیبینیم اونم چون  &#34; ساز &#34; نداره و &#34; دفتر نت &#34; هم ازش گرفتن کلا میره بخوابه . درست مثل وقتی که یه برنامه دیگه اجرا میکنیم و اکتیوتیمون دیگه دیده نمیشه ولی تو recent app میتونیم ببینیم نشسته اونجا واسه خودش استراحت میکنه . مسیر رفتن به اتاق خاموشیاز این اتاق هم میشه رفت اتاق اجرا با عبور از گیت onRestart که کارش چک زدن تو گوش اکتیویتیه تا بیدار بشه و بعدش گیت  خودمون که  &#34; ساز &#34; رو پس میده بهش ، ولی مجبوره از  هم رد بشه و &#34; دفتر نت &#34; رو هم پس میگیره میره میخونه .مسیر برگشتن به اتاق اجرا از اتاق خاموشیدر ضمن از این اتاق به بیرون ساختمان هم راه داره که باید از گیت دیگه ای به نام onDestroy رد بشه که &#34; دفتر نت و ساز و لباس &#34; ازش میگیرن و اینطوری اکتیویتی میره بیرون ساختمون و دیگه ازش خبری نداریم . مسیر خروج از ساختماناگه اکتیویتی ما اونجا لم بده و بخوابه و ما هم بریم یه چند تا خواننده دیگه بیاریم اجرا کنن ، بعدم اونا رو هم بفرستیمشون اتاق خاموشی استراحت کنن ، اگه اتاق خاموشی دیگه جا نداشته باشه مجبوریم اکتیویتی رو بندازیم بیرون و اگه بخواد دوباره بیاد بخونه باز مثل اول دوباره از سه تا گیت رد بشه و  بره بخونه . در واقع مثل وقتی که حافظه رم گوشی پر میشه و اتوماتیک اپ های بلااستفاده رو kill میکنه.راستی تا یادم نرفته ، گیت دیگه هم قبل از گیت onDestroy داریم به نام onSaveInstanceState  که میشه هر اطلاعاتی رو از اکتیویتی بگیریم ، تا تو گیت onCreate  و پارامتر Bundle براش دوباره اماده کنیم . در ضمن اگه وقتی اکتیویتی تو اتاق اجراست و ساختمون رو بچرخونید ( گوشی رو بچرخونید ) ، باید اکتیویتی بره بیرون ساختمون دوباره بیاد و ناچارن یه راه بیشتر نداره از &#34; اتاق اجرا &#34; بره &#34; اتاق سکوت &#34; بعدم بره &#34; اتاق خاموشی &#34; بعد هم بیرون ساختمون و دوباره برگرده از اول و از همه گیت های بین اتاق هایی که گفتیم هم رد میشهدر این مثالی که زدیم و همونطور که در نمای کلی میبینید اتاق ها یه حالت هستند و تو هر بخش گفتیم مثلا چه حالتی تو صفحه موبایل اتفاق می افته ، ولی گیت ها در برنامه نویسی همون متد های معروف تو اکتیویتی هستند . سیستم اندروید بصورت اتوماتیک وقتی وضعیت اکتیویتی تغییر کنه این متد ها رو صدا میزنه و ما هم میتونیم ازشون برای کنترل اکتیویتی استفاده کنیم .چرخه حیات اکتیویتیبزارید یه مثال کاربردی بزنم . فرض کنید اکتیویتی ما یک کرنومتره و داره شروع به شمارش میکنه . حالا اگه اکتیویتی بره تو وضعیت خاموشی و بخوایم کرنومتر متوقف بشه باید تو  کرنومتر متوقف بشه و دوباره تو onRestart یا  کرنومتر رو راه بندازیم.</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sun, 28 Mar 2021 21:27:51 +0430</pubDate></item><item><title>کتاب : عروسک های شکسته</title><link>https://virgool.ir/@morteza.darzi/کتاب-عروسک-های-شکسته-wyhw0bemftpo</link><description>داستان زندگی زنان درباری در ایران تا دوران پهلویعروسک های شکستهاین کتاب در مورد زندگی و اتفاقات پیرامون زنان درباری ایران از دوران قدیم تا زمان پهلوی را شامل میشود . در این کتاب در چند فصل کوتاه ابتدایی با زنان درباری مهم تا زمان قاجاریان آشنا میشوید و خواهید دید که چطور سیاست و دربار در زندگی آنها تاثیر داشته و یا آنها را همچون عروسک های به بازی گرفته و یا آنها را به موجوداتی عجیب تبدیل کرده . در فصل های بعدی به صورت مبسوط در مورد هر یک از زنان درباری دوران پهلوی پرداخته میشود و مادر و خواهر و دختران شاهان پهلوی را معرفی کرده و به سیر زندگی و اتفاقاتی که برایشان افتاده خواهد پرداخت . هر چند همانطور که از عنوان پیداست با پیش زمینه مورد استثمار قرار گرفته شدن زنان در دربار ایران موضوع نوشته شده ولی با توجه به دقتی که در بیان مراجع مختلف تاریخی در مورد هر یک از این زنان پرداخته شده عملا زندگی نامه تاریخی مرجعی خواهد بود که میشود به جو حاکم در دوران های مختلف بر روی زنان ان زمان پی برد . تعداد صفحات : 672</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sun, 30 Aug 2020 23:10:53 +0430</pubDate></item><item><title>کتاب : خاطرات اشرف پهلوی</title><link>https://virgool.ir/@morteza.darzi/کتاب-خاطرات-اشرف-پهلوی-fo5fmzmnavg6</link><description>کتاب خاطرات زندگی اشرف پهلوی از زبان خودشخاطرات اشرف پهلویکتاب زندگی نامه اشرف پهلوی خواهر دوقلوی محمد رضا پهلوی شاه ایران از زبان خودش در زمانی که در آمریکا زندگی میکرد نوشته شده و شرح حال رو از زاویه دید اشرف نشون میده . هر چند کاملا قابل حدس خواهد بود که ایشون داستان رو به صورتی تلطیف شده نقل خواهد کرد ، ولی بعضی از وقایع دربار ایران در اون دوران و اطلاع از جزئیات میتونه تصور درستی از تغییر در مملکت ایران رو بدست بده . در این کتاب اشرف به صراحت از اخلاق و منش رضا شاه صحبت میکنه و از خفقانی که حتی در دوران خانواده به وجود اورده مثل ازدواج های اجباری و بعد هم در دوران تبعید پهلوی اول و اتفاقات بعد از اون رو به تصویر میکشه . از احساس سرخوردگی که از اول در خانواده داشته و اینکه تاثیر زیادی روی محمد رضا میگذاشته . در بعضی از وقایع مثل تبعیدش توسط مصدق ، بدون اوردن دلیلی قانع کننده فقط به بیان اتفاق اکتفا میکنه و میشه فهمید چیزهایی که در مورد اون گفته میشه احتمالا درست بوده . با خودن این کتاب یک سفر تاریخی به درون کاخهای سلطنتی خواهید داشت و میتونید بفهمید سلطنتی ها چطور زندگی میکردند و چطور ازدواج کرده و یا فکر میکردند . چیزهای جالبی از خصوصی ترین مسائل زندگی و حتی دعواهای سلطنتی !!بهرحال خوندن کتاب زندگی نامه خواهر شاه سابق ایران خالی از لطف نخواهد بود اگر علاقه ای به تاریخ هم نداشته باشید . راستی تا یادم نرفته پاورقی های این کتاب هم در نوع خود جالبه !!!تعداد صفحات : 319</description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Fri, 28 Aug 2020 22:55:04 +0430</pubDate></item><item><title>كتاب : حاج آخوند</title><link>https://virgool.ir/@morteza.darzi/كتاب-حاج-آخوند-c1apmgbvw2ks</link><description>كتاب خاطرات يك آخوند واقعي ! با سير و سلوك خاص از زبان عطالله مهاجراني ( سياست مدار سابق ) يك از كتاب هاي زيبايي كه خواندم و لذت بردم و افسوس خوردم . داستان كتاب روايت خاطرات خود نويسنده در روستاي مارون ( مهاجران ) از يك آخوند محلي با سبك و سياق خاص و جداي از تعريف هاي عامه اي كه ما در ذهنمان در مورد يك روحاني مذهبي داريم . آخوندي كه عاشق خيام و فردوسي و حافظ و باباطاهر و سعدي و ... حاج آخونداز اون كتاب هايي ست كه وقتي كنارش ميزاري بهت چشمك ميزنه و بازم دوست داري چند خط بخوني . نحوه روايت نويسنده هم جالبه و در بين روايت يه خاطره پرانتزي باز ميكنه و روايت كوتاهي رو برات تعريف ميكنه كه مرتبط با اين موضوعه و آخرش بدون هيچ گونه اضافه گويي بر ميگردي به اصل داستان ، هرچند بعضي وقتا گيج ميشي الان اون داستان جديده است يا قديميه ولي چون بي آلايش و سريع ميره سر اصل داستان خسته نميشي . حتي بعضي از فصل هاي كتاب در حد چند صفحه است ولي كاملا پر مغز . در تعريف فضاي خاطره هم چون واقعيه و هم خوب تعريف ميشه ، حس ميكني بوي باسلق و كشمش و گلبرگ هاي هلو رو ميتوني از تو ورق هاي كتاب استشمام كني . در طول داستان با حاج اخوند آشنا ميشي و ميفهمي ميشه اينطور هم زندگي كرد . ميشه در گوشه اي كوچك هم شمس بود . ميشه بچه هاي يك روستا رو هم بدون گيوه با خيام آشنا كرد . ميشه هر آنچه داري رو ببخشي . ميشه با شعر هم آخوند بود . چيزي كه اين روزها ما كمتر ميگرديم و پيداش ميكنم همان خودمونه كه داريم و نمي دونيم . من در طول داستان حس كردم دارم داستان شمس تبريزي رو ميخونم .خودم به شخصه بعد از خوندن اين كتاب رفتم و كتاب صوتي رباعيات خيام رو ريختم تو گوشيم و فرصت كه شد گوش ميدم ، كتاب شاهنامه رو گذاشتم تو ليست خريدم تا بار ديگه بهتر بخونمش . گلستان سعدي تازه برام مفهوم پيدا كرده و متوجه شدم آنچه مي گشتم همينجاست . يكي از افسوس هايي كه بعد از خواندن اين كتاب خوردم اين بود كه چرا تفسير هاي شعرا و نقض گويان ايراني دم دست و عامه پسند و خواندني نيست . خودم بارها اشعار حافظ و مولانا و خيام رو شنيده بودم و ميدونستم در اينها بس معاني نهفته ولي تابحال كتابي رو نخونده بودم كه اينا رو زندگي كنه . كتابي رو نخوندم كه بگه رستم در خان اول خوابيده بود و در خان آخر بيدار !اين كتاب را رو در ماه ارديبهشت سال 99 خوندم . كتابي با 282 صفحه . بهتون پيشنهاد ميكنم حتما بخونيد </description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Tue, 26 May 2020 10:10:43 +0430</pubDate></item><item><title>كتاب : خواجه تاجدار</title><link>https://virgool.ir/@morteza.darzi/كتاب-خواجه-تاجدار-s59q83d4fsvq</link><description>درصد كمك به افزايش مطالعه در آمار ويرگول ، به فكرم انداخت كه خودم در سال چقدر واقعا مطالعه ميكنم ؟ بر آن شدم كتابهايي كه ميخونم رو با توضيحاتي تو ويرگول بنويسم تا بتونم محاسبه درستي از درصد مطالعه خودم داشته باشمخواجه تاجدارخوندن رمان هاي تاريخي يه حس خاص داره . اينكه ميدوني تمام چيزهايي كه داري ميخوني اتفاق افتاده و تاريخ هم دقيقا از روي همين اتفاقات به وجود اومده حس زندگي كردن در اون برهه زماني رو به آدم ميده . كتاب خواجه تاجدار رمان تاريخي نوشته ژان گور (jean guèvre ) فرانسوی که توسط ، زنده یاد ذبیح‌الله منصوری ترجمه شده كه آثاري مثل &#34; سينوهه &#34; نيز در كارنامه اين مترجم و نويسنده وجود داره داستان خواجه تاجدار در واقع از زمان تولد آغا محمد خان قاجار در دوره شاهنشاهي نادر شاه افشار تا زمان مرگ اين شاه ايران را به تصوير ميكشه . به تصوير كشيدن واقعا توصيف درستي از اين كتابي است كه تقريبا تمام اتفاقات ريز و درشت مهم و تاثير گذار اون دوران را نوشته و ميتونيم با خوندنش حس كنيم كه زندگي در اون دوران چطور ميگذره . هرچند در نقد هاي اين كتاب اومده كه روايت داستان سعي در قهرمان نشان دادن آغا محمد خان داره ولي به نظرم اينطور نيست خصوصا وقتي در بخش هايي كه در مورد لطفعلي خان زند و سيره و سيماي اون و نحوه كشته شدنش صحبت ميكنه اتفاقا چهره منفي از آغا محمد خان به تصوير كشيده ميشه . ولي هوشمندي خاصي كه آغا محمد خان داشته و در تاريخ هم ثبت شده چيزي نيست كه اين كتاب بخواد بزرگنمايي كنه . چون فردي كه با اينكه خواجه شده ولي تونسته خودش رو به ديگران ثابت كنه و بشه شاه ايران حتما آدم باهوشي بوده .روش روايت تاريخي نويسنده هم به اين صورته كه تمام روايت رو براساس اسناد تاريخي مختلف با ذكر منبع  در مورد اون موضوع بيان ميكنه و در آخر طبق شواهد با نتيجه گيري ، يك روايت رو منطبق برحقيقت انتخاب ميكنه . هر چند پيش اومده در جاهايي نتونسته به نتيجه درستي برسه و فرض رو بر اين ميگيره كه واقعيت ماجرا طبق اين روايت بوده باشه . مثلا يك از سوالات مهم موجود در مورد علت خواجه شدن آغا محمد خان رو با اسناد و مدارك متفاوت كاملا بررسي كرده و تقريبا به يك نتيجه درست ميرسه . اين كتاب جزو كتابهاي قطوري بوده كه تونستم تا اخرش رو بخونم و واقعا لذت بردم . به دوستاني كه اهل تاريخ هستند يا خواندن رمان هاي تاريخي رو دوست دارن حتما پيشنهاد ميشه اينو بخونن . خودم اين كتاب رو در ابتداي سال 97 خوندم . كتابي كه بصورت قرضي بدستم رسيده بود بسيار قديمي بود و كلا در يك جلد نوشته شده بود و بعد از پس دادن اون تصميم گرفتم حتما اين كتاب رو به كتابخونم اضافه كنم كه هنوز موفق نشدم ، اينطور هم كه بررسي كردم معمولا در دو جلد چاپ ميشه . </description><category>مرتضي درزي</category><author>مرتضي درزي</author><pubDate>Sat, 16 May 2020 11:12:29 +0430</pubDate></item></channel></rss>