<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های حمید امیرحاجلو</title><link>https://virgool.ir/feed/@hamid.t711</link><description>به برنامه نویسی اندروید و تکنولوژی روز دنیا علاقمندم</description><language>fa</language><pubDate>2026-11-11 15:43:29</pubDate><image><url>https://files.virgool.ir/upload/users/304467/avatar/wi3Bdw.png</url><title>حمید امیرحاجلو</title><link>https://virgool.ir/@hamid.t711</link></image><item><title>مفهوم RPC به همراه مثال gRPC در اندروید</title><link>https://virgool.ir/@hamid.t711/مفهوم-rpc-به-همراه-مثال-grpc-در-اندروید-erdpqwcalvla</link><description>rpc programmingدر محاسبات توزیع شده ، یک فراخوان از راه دور RPC زمانی است که یک برنامه کامپیوتری باعث می شود یک رویه (زیر برنامه) در فضای آدرس دیگری (معمولاً در رایانه دیگری در شبکه مشترک) اجرا شود ، به گونه ای کدنویسی شود که گویی درون برنامه خودمان است، بدون اینکه برنامه نویس به طور صریح جزئیات تعامل از راه دور را رمزگذاری کند. به این معنی که برنامه نویس اساساً همان کد را می نویسد ، چه زیرروال محلی برنامه اجرا باشد ، چه از راه دور. این نوعی تعامل بین سرویس گیرنده و سرور است (تماس گیرنده مشتری است ، مجری آن سرور است) ، که معمولاً از طریق سیستم ارسال پیام و پاسخ درخواست اجرا می شود.در الگوی برنامه نویسی شی گرا ، RPC ها با فراخوانی متد از راه دور نشان داده می شوند. مدل RPC حاکی از سطحی از شفافیت مکان است.RPC یک پروتکل درخواست و پاسخ است. RPC توسط سرویس گیرنده آغاز می شود ، که برای اجرای یک رویه مشخص با پارامترهای ارائه شده ، یک پیام درخواست به یک سرور راه دور شناخته شده ارسال می کند. سرور راه دور پاسخی برای سرویس گیرنده ارسال می کند و برنامه به روند خود ادامه می دهد. در حالی که سرور در حال پردازش تماس است ، سرویس گیرنده مسدود شده است (منتظر می ماند تا سرور پردازش را قبل از شروع مجدد اجرا به پایان برساند) ، مگر اینکه مشتری یک درخواست ناهمزمان مانند XMLHttpRequest را به سرور ارسال کند.ترتیب وقایع کلاینت (متدها- تعریف  IDL- مارشالینگ - درگاه خروجی)1- استاب فراخوانی می شود، این فراخوان یک فراخوان رویه محلی است که پارامترهای آن به روش عادی روی پشته رانده می شوند.2-  استاب مشتری پارامترها را در یک پیام قرار داده و برای ارسال پیام یک تماس سیستمی برقرار می کند. بسته بندی پارامترها را مارشالینگ می نامند.3- سیستم عامل کلاینت پیام را از به سرور می فرستد.4- سرور بسته های ورودی را به اسکلت سرور منتقل می کند.5- سرور پارامترهای پیام را باز می کند. باز کردن بسته بندی پارامترها unmarshalling نامیده می شود.6- سرانجام، سرور متدها خواسته شده را فراخوانی می کند. پاسخ ، همان مراحل را در جهت معکوس دنبال می کند.سازوکارهای تماس استانداردبرای دسترسی کلاینت های مختلف به سرورها ، تعدادی سیستم استاندارد RPC ایجاد شده است. اکثر اینها از زبان توصیف رابطIDL) ) استفاده می کنند تا سیستم عامل های مختلف بتوانند با RPC تماس بگیرند. سپس می توان از پرونده های IDL برای ایجاد کد جهت ایجاد ارتباط بین سرویس گیرنده و سرورها استفاده کرد.تعاریف مرتبطمحاسبات توزیع شده :رشته ای از علوم کامپیوتر است که سیستم های توزیع شده را مطالعه می کند. سیستم توزیع شده سیستمی است که اجزای آن (متد ها ، داده ها ، و محاسبات مخصوص و...) بر روی شبکه های مختلف بر روی سیستم های دیگر قرار دارند که با انتقال پیام به یکدیگر ارتباط برقرار کرده و اقدامات خود را هماهنگ می کنند.استاباستاب در محاسبات توزیع شده قطعه ای از کد است که پارامترهای منتقل شده بین کلاینت و سرور را طی یک تماس از راه دور (RPC) تبدیل می کند. استاب تبدیل پارامترها را از طریق زبان شرح ارتباط IDL انجام می دهند ، بنابراین یک تماس رویه از راه دور مانند یک تماس عملکرد محلی برای رایانه از راه دور به نظر می رسد. در استاب مجموعه ای از اطلاعات با نام سرفایل یا هدر ، بین سرور و کلاینت ردوبدل میشود که به درک برنامه از نوع داده ها ، نوع درخواست، زمان و... کمک میکند.اسکلت یا skeletonشی سمت سرور که در ارتباطات شی توزیع شده شرکت می کند به عنوان اسکلت شناخته می شود (یا استاب: اصطلاح در اینجا از آن اجتناب می شود). یک اسکلت به عنوان دروازه اشیا سمت سرور عمل می کند و کلیه درخواست های ورودی مشتری از طریق آن هدایت می شود.مارشالینگ و آنمارشالینگ :مارشال سازی فرآیند تبدیل اشیاء حافظه به یک قالب داده مناسب برای ذخیره سازی یا انتقال است و معمولاً هنگامی که داده ها باید بین قسمتهای مختلف یک برنامه یا به برنامه دیگری منتقل شوند ، از آن استفاده می شود.مانند تبدیل یک کلاس به یک فایل json یا .xml در برخی زبانها مانند جاوا و پایتون، این اصطلاح برای نوع خاصی از &#34;سریال سازی&#34; (تبدیل شی به بایت و برعکس ) استفاده می شودIDLاز IDL ها معمولاً در نرم افزار تماس از راه دور استفاده می شود. در این موارد ممکن است ماشین های موجود در دو انتهای پیوند از سیستم عامل های مختلف و زبان های رایانه ای استفاده کنند. IDL ها پلی بین دو سیستم مختلف ارائه می دهند.نمونه های معروف ان : JSON – protocol buffer – xml و...پروتکل بافر (protocol buffer)نوع جدیدی از  : IDLیک مکانیزم قابل توسعه مستقل از زبان و پلت فرم، جهت سریال سازی داده های ساخت یافته است. در این سیستم ما یک فایل با پسوند proto. که در آن توصیفی از ساختار داده ای را که می خواهیم ذخیره کنیم (طرح از پیش تعریف شده) می نویسیم. کامپایلرprotoc یک کلاس ایجاد می کند که رمزگذاری و تجزیه خودکار داده های بافر را با یک قالب باینری کارآمد پیاده سازی می کند. کلاس تولید شده ، setter &amp; getter را برای فیلدهایی که بافر را تشکیل می دهند فراهم می کند و از جزئیات خواندن و نوشتن بافر به عنوان یک واحد مراقبت می کند.پیاده سازی  RPCدر android با gRPCتعریف gRPCgRPC یک چارچوب RPC مدرن ، منبع باز و با عملکرد بالا است که می تواند در هر محیطی اجرا شود. این برنامه می تواند با پشتیبانی قابل جابجایی برای توازن بار ، ردیابی ، بررسی سلامت و احراز هویت ، خدمات را در مراکز داده و از طریق آنها متصل کند. همچنین برای اتصال دستگاهها ، برنامه های تلفن همراه در بروزترین نسخه های منتشرشده قابل استفاده است.چون این یک RPC  است، باعث می شود درخواستهای مرتبط با شبکه دقیقا مشابه درخواست های محلی بنظر برسد و تعامل با سرور محدود به متد های منبع HTTP  نخواهد شد و کد طبیعی تر به نظر می رسد.این روش باعث کاهش استفاده از منابع می شود و برای دستگاه هایی که مصرف باتری در انها مهم است بسیار بهینه است. با استفاده از قابلیت جریان سازی داخلی این روش ، سرویس گیرنده و سرور می توانند پیام ها را به طور همزمان بخوانند و بنویسند و بدون ردیابی شناسه منابع ، مشترک یکدیگر شوند. این باعث انعطاف پذیری بیشتر اجرای برنامه شما می شود.gRPC با استفاده از بافرها به عنوان روش سریال سازی و معکوس سریال سازی، با پلاگین بهینه سازی شده برای محیط های مختلف عرضه شده است.در مقایسه با قالب مبتنی بر متن مانند JSON)) ، Protobuf از نظر سرعت مارشال سازی و اندازه کد ، تبادل داده کارآمدتری را ارائه می دهد که استفاده از آن را در محیط های تلفن همراه مناسب تر می کند.نمای کلی استفادهاستفاده از gRPC برای برنامه های Android شامل چهار مرحله است:1 - تعریف سرویس RPC با پروتکل بافر و ایجاد رابط های سرور و کلاینت gRPC.translatIDL.protoservice Translator{&#xA;  rpc Translate (TranslateRequest) returns (TranslateResponse){}&#xA;}&#xA;&#xA;message TranslateRequest{&#xA;  string text = 1;&#xA;}&#xA;&#xA;message TranslateResponse{&#xA;  string text = 1;&#xA;}2 - کانالی  که به عنوان واسطه ای برای تماس های RPC بین کلاینت و سرور باشد.TranslateChannel.ktprivate fun channel(): ManagedChannel {&#xA;    val builder = ManagedChannelBuilder.forAddress((&#34;https://www.google.com&#34;)&#xA;    return builder.executor(Dispatchers.IO.asExecutor()).build()&#xA;}3 - یک Stub به عنوان نقطه ورود برای شروع تماس های RPCMainActivity.ktprivate val translator by lazy { TranslatorGrpc.newBlockingStub(channel()) }4 - مشابه صدا زدن متدهای درون برنامه، فراخوانی RPC و صدا زدن متدهای روی سرور:val request = TranslateRequest.newBuilder().setText(text).build()&#xA;&#xA;val response = translator.translate(request)&#xA;&#xA;tvResponse.text = response.textبا gRPCمی توانیم سرویس خود را یک بار در یک پرونده .proto تعریف کنیم و کلاینت و سرور را به هر زبان پشتیبانی شده از gRPCتولید کنیم که به نوبه خود در محیط هایی از سرورهای داخل یک مرکز داده بزرگ تا گوشی شما قابل اجرا است - تمام پیچیدگی های ارتباط بین زبان ها و محیط های مختلف توسط gRPC انجام می شود.همچنین از تمام مزایای کار با بافر پروتکل ، از جمله سریال سازی کارآمد ، IDL ساده و به روزرسانی آسان رابط بهره مند می شویم.فایل .proto بیان تمام نیازمندی های برنامه ماست و نمونه ی کلاینت و سرور دقیقا مشابه یکدیگر می باشند.کافیست در کلاینت تعریف کنیم و آن را به پوشه پروژه سرور، که با پی اچ پی ران میشود کپی کنیم یا برعکس!</description><category>حمید امیرحاجلو</category><author>حمید امیرحاجلو</author><pubDate>Mon, 30 Nov 2020 12:07:14 +0330</pubDate></item><item><title>برنامه نویسی  RPC در Kotlin android با کتابخانه Retrofit  و سرور PHP</title><link>https://virgool.ir/@hamid.t711/برنامه-نویسی-rpc-در-kotlin-android-با-کتابخانه-retrofit-و-سرور-php-xkhjyvlfmwkf</link><description>تنها چیزی که لازم است بدانیم این است که چه عنصری از سمت سرور دریافت می شود و سپس کلاس داده متناظر با آن را ایجاد میکنیم .کلاس داده را هم میتوان بصورت دستی و هم بصورت خودکار، با دادن فایل IDL  دریافتی( که در اینجا منظور JSON  است )به سایتهای تبدیل کننده خیلی سریع برای زبان مورد نظر دریافت کرد و در برنامه از آن استفاده کرد.با این کار اکنون ما یک زبان مشترک که ارتباط بین سرور PHP و کلاینت Kotlin را برقرار می کند داریم.بعنوان نمونه، ما یک داده دریافتی از سرور با لینک https://jsonplaceholder.typicode.com/posts و فرمت JSONداریم.کلاس داده متناظر با این داده که قابل استفاده در زبان مورد نظر ماست به شکل زیر خواهد بود:data class Post(val userId: Int, val id: Int, val title: String, val body: String )کتابخانه rpc  ما که در اینجا Retrofit  است ، یک مبدل (converter-gson) دریافت می کند که بصورت خودکار کلاس داده ما را به فرمت قابل فهم برای سرور تبدیل می کند و داده دریافتی را تبدیل به شیئی از نوع کلاس داده ما میکند و برای استفاده دقیقا مثل کلاس های محلی می شود. مبدل ها را می توان برای پشتیبانی از انواع دیگر IDL اضافه کرد. 6 ماژول محبوب سریال سازی قابل پشتیبانی عبارتند از:· Gson: com.squareup.retrofit2:converter-gson· Jackson: com.squareup.retrofit2:converter-jackson· Moshi: com.squareup.retrofit2:converter-moshi· Protobuf: com.squareup.retrofit2:converter-protobuf· Wire: com.squareup.retrofit2:converter-wire· Simple XML: com.squareup.retrofit2:converter-simplexml· JAXB: com.squareup.retrofit2:converter-jaxb· Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalarsبرای برقراری ازتباط با سرور نیاز به interface داریم که در آن از متدهای HTTP  استفاده می شود. تمام متد هایی که میتواند درPHP  و هدر استفاده شود پشتیبانی می شود.  در اینجا ما با GET لیستی از پست ها دریافت خواهیم کرد:interface PostService {&#xA;&#xA;&#xA;&#xA;    @GET(&#34;posts&#34;)&#xA;&#xA;    fun getPost(): Call&lt;List&lt;Post&gt;&gt;&#xA;&#xA;}اکنون یک کلاس کمکی برای برقراری ارتباط با سرور و ایجاد یک سرویس جهت استفاده آن در کد خودمان ، نیاز داریم که بصورت زیر تعریف می شود:PostApi.ktobject PostApi {&#xA;&#xA;    &#xA;&#xA;    private val retrofit = Retrofit.Builder()&#xA;&#xA;        .baseUrl(&#34;https://jsonplaceholde.typicode.com/&#34;)&#xA;&#xA;        .addConverterFactory(GsonConverterFactory.create())&#xA;&#xA;        .build()&#xA;&#xA;    val service = retrofit.create(PostService::class.java)&#xA;&#xA;}همه چیز تمام شد فقط کافیست درون کدمان جایی که میخواهیم متدهای ریموت را صدا بزنیم ،از طریق سرویس به متد های HTTPدسترسی پیدا کنیم:PostApi.service.getPost().execute().body().apply {&#xA;&#xA;&#xA;&#xA;        this?.forEach {&#xA;&#xA;            println(it.userId)&#xA;&#xA;            println(it.id)&#xA;&#xA;            println(it.title)&#xA;&#xA;            println(it.body)&#xA;&#xA;        }&#xA;&#xA;    }</description><category>حمید امیرحاجلو</category><author>حمید امیرحاجلو</author><pubDate>Thu, 26 Nov 2020 15:13:41 +0330</pubDate></item><item><title>معماری ویو مدل در اندروید</title><link>https://virgool.ir/@hamid.t711/معماری-ویو-مدل-در-اندروید-dgukwrsudags</link><description>این راهنما شامل بهترین روش ها و معماری توصیه شده برای ساختن برنامه های قوی و با کیفیت ساخت بالاست.تجربیات کاربر برنامه تلفن همراهدر اکثر موارد ، برنامه های دسک تاپ دارای یک نقطه ورودی از یک دسک تاپ یا راه انداز برنامه هستند ، سپس به صورت یک فرآیند یکپارچه اجرا می شوند. از طرف دیگر ، برنامه های اندروید ساختار بسیار پیچیده تری دارند. یک برنامه معمولی اندروید شامل چندین کامپوننت است ، از جمله  (activity)،  (fragment)،  (service)،  (content provider) ،(broadcast receiver)این مولفه ها را در مانیفست برنامه خود اعلام می کنید . سپس سیستم عامل Android با استفاده از این پرونده تصمیم می گیرد که چگونه برنامه شما را در تجربه کاربری کلی دستگاه ادغام کند. با توجه به اینکه یک برنامه آندروید که به درستی نوشته شده است ، دارای چندین مولفه است و کاربران اغلب در مدت زمان کوتاهی با چندین برنامه ارتباط برقرار می کنند ، برنامه ها باید با انواع مختلف کارها و وظایف کاربر محور سازگار شوند.به عنوان مثال ، در نظر بگیرید چه اتفاقی می افتد وقتی عکسی را در برنامه شبکه اجتماعی مورد علاقه خود به اشتراک می گذارید:1. این برنامه intent دوربین را صدا میزند. سپس سیستم عامل Android یک برنامه دوربین را برای رسیدگی به درخواست راه اندازی می کند. در این مرحله ، کاربر برنامه شبکه های اجتماعی را ترک کرده است ، اما تجربه آنها هنوز یکپارچه است.2. برنامه دوربین ممکن است اهداف دیگری مانند راه اندازی انتخابگر پرونده را که ممکن است برنامه دیگری را راه اندازی کند ، ایجاد کند.3. در نهایت ، کاربر به برنامه شبکه های اجتماعی بازمی گردد و عکس را به اشتراک می گذارد.در هر مرحله ، ارتباط کاربر با برنامه می تواند با یک تماس تلفنی یا اعلان قطع شود. پس از بازگشت از تماس، کاربر انتظار دارد كه بتواند به فرآیند اشتراک گذاری عکس برگردد و از سر بگیرد. این رفتار جابجایی و پریدن بین برنامه، در دستگاه های تلفن همراه معمول است ، بنابراین برنامه شما باید این جریانات را به درستی کنترل کند.به خاطر داشته باشید که دستگاه های تلفن همراه نیز محدودیت منابع ( حافظه و رم ) دارند ، بنابراین در هر زمان ، سیستم عامل ممکن است برخی از فرایندهای برنامه را از بین ببرد تا جای فرایندهای جدید را بگیرد.با توجه به شرایط این محیط ، این امکان وجود دارد که اجزای برنامه شما به صورت جداگانه و خارج از سفارش بسته شوند و سیستم عامل یا کاربر می تواند آنها را در هر زمان از بین ببرد. از آنجا که این رویدادها تحت کنترل شما نیستند ، شما نباید هیچ داده یا وضعیت برنامه را در اجزای برنامه خود ذخیره کنید و اجزای برنامه شما نباید به یکدیگر وابسته باشند.اصول معماری مشترکاگر شما نباید از اجزای برنامه برای ذخیره داده ها و حالت برنامه استفاده کنید ، چگونه باید برنامه خود را طراحی کنید؟تفکیک رابطه هامهمترین اصلی که باید رعایت شود تفکیک رابطه است . نوشتن تمام کدهای خود در یک Activity یا Fragment یک اشتباه معمول است . این کلاسهای مبتنی بر رابط کاربر فقط باید دارای منطقی باشند که تعاملات رابط کاربر و سیستم عامل را مدیریت می کند. با پایین نگه داشتن حجم کدهای این کلاس ها تا حد ممکن ، می توانید از بسیاری از مشکلات مربوط به چرخه زندگی جلوگیری کنید.به خاطر داشته باشید که شما پیاده سازی Activity وFragment  را ندارید، بلکه این فقط کلاسهای مرتبط با هم است که قرارداد بین سیستم عامل Android و برنامه شما را نشان می دهد. سیستم عامل می تواند در هر زمان بر اساس تعاملات کاربر یا به دلیل شرایط سیستم مانند کمبود حافظه ، آنها را از بین ببرد. برای ارائه یک تجربه کاربری رضایت بخش و تجربه نگهداری برنامه با مدیریت بیشتر ، بهتر است وابستگی خود را به آنها به حداقل برسانید.رابط کاربری را با یک مدل اجرا کنیدیک اصل مهم دیگر این است که شما باید UI خود را از یک مدل ، ترجیحاً یک مدل پایدار(دیتابیس) راه اندازی کنید. مدل ها اجزایی هستند که مسئولیت رسیدگی به داده های یک برنامه را دارند. آنها از View ، اشیا و اجزای برنامه موجود در برنامه شما مستقل هستند ، بنابراین تحت تأثیر چرخه حیات برنامه و نگرانی های مرتبط با آن نیستند.پایداری به دلایل زیر ایده آل است:● اگر سیستم عامل Android برنامه شما را برای آزاد کردن منابع از بین ببرد ، کاربران شما داده خود را از دست نمی دهند.● برنامه شما در مواردی که اتصال شبکه منقطع باشد یا در دسترس نباشد ، به کار خود ادامه می دهد.با قرار دادن برنامه خود تحت کلاسهای مدل با مسئولیت کاملاً مشخص مدیریت داده ها ، برنامه شما تست پذیری و سازگاری بیشتری دارد.معماری برنامه پیشنهادیدر این بخش ، ما نشان می دهیم که چگونه با استفاده از یک مورد استفاده از انتها به انتها ، برنامه را با استفاده از اجزای معماری ساختار می دهیم .توجه: وجود یک روش برای نوشتن برنامه ها که برای هر سناریو بهترین نتیجه را داشته باشد غیرممکن است. همانطور که گفته شد ، این معماری توصیه شده نقطه شروع خوبی برای بیشتر شرایط و گردش کار است. اگر در حال حاضر روش خوبی برای نوشتن برنامه های اندروید دارید که از اصول معماری رایج پیروی می کند ، نیازی به تغییر آن ندارید.تصور کنید در حال ساخت رابط کاربر هستیم که نمایه کاربری را نشان می دهد. ما برای واکشی داده ها برای یک نمایه مشخص از یک backend خصوصی و REST API استفاده می کنیم.بررسی اجمالیبرای شروع ، نمودار زیر را در نظر بگیرید ، که نشان می دهد چگونه همه ماژول ها پس از طراحی برنامه باید با یکدیگر تعامل داشته باشند:توجه داشته باشید که هر مولفه فقط به مولفه یک سطح زیر آن بستگی دارد. به عنوان مثال ، activity/fragment ها فقط به یک viewModel بستگی دارد Repository تنها کلاسی است که به چندین کلاس دیگر بستگی دارد. در این مثال ، Repository به یک مدل داده دیتابیس داخلی و یک منبع داده تغذیه شونده از راه دور بستگی دارد.این طراحی یک تجربه کاربری سازگار و دلپذیر را ایجاد می کند. صرف نظر از اینکه کاربر چند دقیقه یا چند روز بعد از آخرین باری که آن برنامه را بسته به برنامه بازگردد ، بلافاصله اطلاعات کاربر را که به صورت محلی در جریان است ، می بیند. اگر این داده ها قدیمی باشند ، ماژول Repository شروع به بروزرسانی داده ها در پس زمینه می کند.رابط کاربری ایجاد کنید.برای راه اندازی رابط کاربری ، دیتا مدل ما باید عناصر داده زیر را در خود داشته باشد:شناسه ای برای هر کاربر(UserId).  بهتر است این اطلاعات را با استفاده از آرگومان های فرگمنت انتقال دهید. اگر سیستم عامل Android روند ما را از بین ببرد ، این اطلاعات حفظ می شود ، بنابراین دفعه بعدی که برنامه ما دوباره راه اندازی می شود ، ID در دسترس است.کلاس داده ای که جزئیات مربوط به کاربر را در خود جای داده است.(userObject)ما برای حفظ این اطلاعات از یک UserProfileViewModel، که بر اساس مولفه معماری ViewModel  است استفاده می کنیم.یک شی ViewModel داده ها را برای یک UI خاص ، مانند یک فرگمنت یا اکتیویتی فراهم می کند و شامل منطق مدیریت داده برای ارتباط با مدل است. به عنوان مثال ، ViewModel می تواند برای بارگذاری داده ها با اجزای دیگر ارتباط بگیرد و می تواند درخواست های کاربر را برای اصلاح داده ها ارسال کند. این ViewModel از اجزای رابط کاربری چیزی نمی داند ، بنابراین تحت تأثیر تغییرات پیکربندی ، مانند ایجاد مجدد فعالیت هنگام چرخاندن دستگاه ، قرار نمی گیرد.اکنون پرونده های زیر را تعریف کرده ایم:● user_profile.xml : تعریف طرح UIبرای صفحه.● UserProfileFragment : کنترل کننده UI که داده ها را نمایش می دهد.● UserProfileViewModel : کلاسی که داده ها را برای مشاهده آماده می کند و به تعاملات کاربر واکنش نشان می دهد.قطعه کد زیر محتوای اولیه این پرونده ها را نشان می دهد. (فایل لایه برای سادگی حذف شده است)UserProfileViewModelclass UserProfileViewModel : ViewModel() {&#xA; val userId : String = TODO()&#xA; val user : User = TODO()&#xA;}UserProfileFragmentclass UserProfileFragment : Fragment() {&#xA; &#xA; private val viewModel: UserProfileViewModel by viewModels()&#xA; &#xA; override fun onCreateView(&#xA;       inflater: LayoutInflater, container: ViewGroup?,&#xA;       savedInstanceState: Bundle?&#xA;   ): View {&#xA; return inflater.inflate(R.layout.main_fragment, container, false)&#xA;   }&#xA;}اکنون که این ماژول های کد را داریم ، چگونه آنها را به هم متصل کنیم؟ از این گذشته ، وقتی فیلد user در UserProfileViewModel مقداردهی می شود ، ما به روشی برای اطلاع رسانی UI نیاز داریم.برای دستیابی به user، ViewModel های ما نیاز دارند که به آرگومان های Fragment دسترسی پیدا کنند. ما می توانیم آنها را از Fragment منتقل کنیم یا راه بهتر استفاده از ماژول SavedState است، می توانیم ViewModel خود را مستقیماً بخوانیم:توجه: SavedStateHandle به ViewModel اجازه می دهد تا به state و آرگومان های Fragment یا Activity مرتبط دسترسی پیدا کند.// UserProfileViewModelvaluserId : String = savedStateHandle[&#34;uid&#34;] ?:&#xA; throw IllegalArgumentException(&#34;missing user id&#34;)&#xA; val user : User = TODO()&#xA;}&#xA; &#xA; // UserProfileFragment&#xA; private valviewModel: UserProfileViewModel by viewModels(&#xA;   factoryProducer = { SavedStateVMFactory(this) }&#xA;   ...&#xA;)اکنون ما باید هنگام به دست آوردن شی کاربر ، Fragment خود را مطلع کنیم. اینجاست که مولفه LiveData در معماری ظاهر می شود.LiveData یک نگهدارنده داده با قابلیت مشاهده تغییرات داده است. سایر مولفه های برنامه شما می توانند تغییرات اشیا را با استفاده از این نگهدارنده بدون ایجاد مسیرهای وابستگی صریح و سخت بین آنها کنترل کنند. مولفه LiveDataهمچنین به چرخه حیات اجزای برنامه شما - مانند اکتیویتی ها ، فرگمنت ها و سرویس ها – متصل و همگام می شود و داده ها را شامل منطق پاکسازی برای جلوگیری از نشت اشیا و مصرف زیاد حافظه می کند.توجه: اگر همچنان از کتابخانه ای مانند RxJava استفاده می کنید ، می توانید به جای LiveData از آنها استفاده کنید. وقتی از کتابخانه ها و رویکردهایی مانند اینها استفاده می کنید ، مطمئن شوید که چرخه حیات برنامه خود را به درستی اداره می کنید.برای ترکیب کردن مولفه LiveDataدر برنامه خود ، ما نوع داده user  را به  LiveData&lt;User&gt; تغییر می دهیم، اطلاعات هنگام بروزرسانی به آنها اطلاع داده می شود. بعلاوه ، از آنجا که این LiveData از چرخه حیات آگاه است ، پس از اینکه دیگر نیازی به ایجاد کننده نیست ، به طور خودکار پاک می کند.UserProfileViewModelclass UserProfileViewModel(&#xA;   savedStateHandle: SavedStateHandle&#xA;) : ViewModel() {&#xA; val userId : String = savedStateHandle[&#34;uid&#34;] ?:&#xA; throw IllegalArgumentException(&#34;missing user id&#34;)&#xA; val user : LiveData&lt;User&gt; = TODO()&#xA;}اکنون ما UserProfileFragment برای رصد داده ها و به روزرسانی رابط کاربر اصلاح می کنیم:UserProfileFragmentoverride funonViewCreated(view: View, savedInstanceState: Bundle?) {&#xA; super.onViewCreated(view, savedInstanceState)&#xA;   viewModel.user.observe(viewLifecycleOwner) {&#xA; // update UI&#xA;   }&#xA;}هر بار که اطلاعات پروفایل کاربر به روز می شود ، کال بک d() فراخوانی می شود و UI آپدیت می شود.اگر با کتابخانه های دیگری که در آن از بازگشت پاسخ قابل مشاهده استفاده می شود ، آشنا باشید ، ممکن است متوجه شده باشید که ما متد () این فرگمنت را برای جلوگیری از مشاهده داده ها ، فراخوانی نکردیم. این مرحله برای LiveData لازم نیست زیرا از چرخه حیات آگاه است ، این بدان معنی است که کال بک d() را فراخوانی نمی کند  مگر اینکه فرگمنت در حالت فعال باشد. یعنی () دریافت کرده  اما هنوز () دریافت نکرده است. LiveData همچنین هنگام فراخوانی متد onDestroy () فرگمنت ، مشاهده کننده را به طور خودکار حذف می کند.واکشی داده هااکنون که از LiveDataبرای اتصال UserProfileViewModel به UserProfileFragment استفاده کردیم  ، چگونه می توانیم اطلاعات پروفایل کاربر را واکشی کنیم؟برای این مثال ، ما فرض می کنیم که backend ما REST API را فراهم می کند. ما از کتابخانه Retrofit برای دسترسی به بکند خود استفاده می کنیم ، اگرچه شما آزاد هستید که از کتابخانه دیگری استفاده کنید که با همان هدف انجام شود.در اینجا تعریف ما از Webservice آن است که با بکند ما ارتباط برقرار می کند:WebServiceinterface Webservice {&#xA; @GET(&#34;/users/{user}&#34;)&#xA; fun getUser(@Path(&#34;user&#34;) userId: String): Call&lt;User&gt;&#xA;}اولین ایده برای اجرای ViewModel ممکن است شامل تماس مستقیم سرویس Webs برای واکشی داده ها و اختصاص این داده ها به شی LiveData ما باشد. این طراحی موثر است ، اما با استفاده از آن ، برنامه ما با رشد بیشتر و دشوارتر می شود. این مسئولیت بیش از حد به کلاس UserProfileViewModel می دهد ، که اصل تفکیک نگرانی ها را نقض می کند. علاوه بر این ، دامنه ViewModel به چرخه حیات Activity یا Fragment گره خورده است ، به این معنی که با پایان یافتن چرخه حیات شی UI مرتبط ، داده های سرویس Webs از دست می رود. این رفتار یک تجربه کاربری نامطلوب ایجاد می کند.در عوض ، ViewModel ما فرآیند واکشی داده را به یک ماژول جدید ، مخزن ، تفویض می کند.ماژول های مخزن عملیات داده را مدیریت می کنند. آنها یک API تمیز ارائه می دهند تا بقیه برنامه بتواند به راحتی این داده ها را بازیابی کند. آنها می دانند که داده ها را از کجا بگیرند و APIبرای بروزرسانی داده ها چه چیزی را فراخوانی می کند. می توانید مخازن را به عنوان واسطه بین منابع مختلف داده مانند مدل ها ، وب سرویس و حافظه کش در نظر بگیرید.کلاس ما که در قطعه کد زیر نشان داده شده است ، از نمونه وب سرویس برای واکشی داده های کاربر استفاده می کند:UserRepositoryclass UserRepository {&#xA; private val webservice: Webservice = TODO()&#xA; // ...&#xA; fun getUser(userId: String): LiveData&lt;User&gt; {&#xA; val data = MutableLiveData&lt;User&gt;()&#xA;       webservice.getUser(userId).enqueue(object :Callback&lt;User&gt; {&#xA; override fun onResponse(call: Call&lt;User&gt;, response: Response&lt;User&gt;) {&#xA;               data.value = response.body()&#xA;           }&#xA; // Error case is left out for brevity.&#xA; override funonFailure(call: Call&lt;User&gt;, t: Throwable) {&#xA;               TODO()&#xA;           }&#xA;       })&#xA; return data&#xA;   }&#xA;}حتی اگر ماژول مخزن غیرضروری به نظر برسد ، یک هدف مهم را دنبال می کند: منابع داده را از بقیه برنامه جدا می کند. اکنون ، ما در  UserProfileViewModelنمی دانیم که چگونه داده ها واکشی می شوند ، بنابراین می توانیم ویو مدل را با داده های به دست آمده از چندین پیاده سازی مختلف جمع آوری داده ارائه دهیم.توجه: ما برای خطای ساده پرونده خطای شبکه را کنار گذاشته ایم. برای یک اجرای جایگزین که خطاها و وضعیت بارگیری را آشکار می کند ، به ضمیمه مراجعه کنید : نمایش وضعیت شبکه .وابستگی ها را بین اجزا مدیریت کنید● تزریق وابستگی : تزریق وابستگی به کلاسها اجازه می دهد تا وابستگی های خود را بدون ساختن آنها تعریف کنند. در زمان اجرا ، کلاس دیگری مسئول تأمین این وابستگی ها است. ما کتابخانه Dagger 2 را برای پیاده سازی تزریق وابستگی در برنامه های Android توصیه می کنیم . دگر 2 با حرکت در درخت وابستگی به طور خودکار اشیا را می سازد و تضمین های مربوط به زمان کامپایل را در مورد وابستگی ها ارائه می دهد.● سرویس یاب : الگوی یاب سرویس ، رجیستری را فراهم می کند که در آن کلاس ها می توانند وابستگی های خود را به جای ساختن ، بدست آورند.این الگوها به شما امکان می دهند کد خود را مقیاس بندی کنید زیرا الگوهای روشنی برای مدیریت وابستگی ها بدون تکثیر کد یا افزودن پیچیدگی ارائه می دهد. علاوه بر این ، این الگوها به شما امکان می دهد تا به سرعت بین پیاده سازی های آزمایش و تولید داده جابجا شوید.class UserProfileViewModel @Inject constructor(&#xA;   savedStateHandle: SavedStateHandle,&#xA;   userRepository: UserRepository&#xA;) : ViewModel() {&#xA; val userId : String = savedStateHandle[&#34;uid&#34;] ?:&#xA; throw IllegalArgumentException(&#34;missing user id&#34;)&#xA; val user : LiveData&lt;User&gt; = userRepository.getUser(userId)&#xA;}داده های حافظه پنهانمشکل اصلی درپیاده سازی Repository این است که پس از آنکه داده ها را از قسمت بکند ما دریافت کرد، این داده ها را در هیچ کجا ذخیره نمی کند. بنابراین ، اگر کاربر از آن فرگمنت خارج شود  ، سپس به آن بازگردد ، برنامه ما باید داده ها را دوباره واکشی کند ، حتی اگر تغییری نکرده باشد.به دلایل زیر این طرح بهینه نیست:● پهنای باند با ارزش شبکه را هدر می دهد.● کاربر را وادار می کند منتظر تکمیل درخواست جدید شود.برای رفع این کمبودها ، ما یک منبع داده جدید به مخزن خود اضافه می کنیم ، این اشیا موجود در حافظه را ذخیره می کند:UserRepository@Singleton&#xA; class UserRepository @Inject constructor(&#xA; private val webservice: Webservice,&#xA; private val userCache: UserCache) {fun getUser(userId: String): LiveData&lt;User&gt; {&#xA; val cached : LiveData&lt;User&gt; = userCache.get(userId)&#xA; if (cached != null) {&#xA; returncached&#xA;       }&#xA; val data = MutableLiveData&lt;User&gt;()&#xA; &#xA;       userCache.put(userId, data)&#xA; &#xA;       webservice.getUser(userId).enqueue(object : Callback&lt;User&gt; {&#xA; override funonResponse(call: Call&lt;User&gt;, response: Response&lt;User&gt;) {&#xA;               data.value = response.body()&#xA;           }&#xA; &#xA; override funonFailure(call: Call&lt;User&gt;, t: Throwable) {&#xA;               TODO()&#xA;           }&#xA;       })&#xA; return data&#xA;   }&#xA;}چرخه حیات دادهبا استفاده از اجرای فعلی ما ، اگر کاربر دستگاه را بچرخاند یا خارج شود و بلافاصله به برنامه برگردد ، UI موجود فوراً قابل مشاهده می شود زیرا مخزن داده ها را از حافظه کش ما بازیابی می کند.با این حال ، چه اتفاقی می افتد اگر کاربر برنامه را ترک کند و ساعاتی بعد ، پس از اینکه سیستم عامل Android فرآیند را از بین برد ، دوباره برگردد؟ با تکیه بر پیاده سازی فعلی خود در این شرایط ، باید داده ها را دوباره از شبکه دریافت کنیم. این فرایند بازیابی فقط یک تجربه کاربری بد نیست. همچنین بی مصرف است زیرا داده های ارزشمند تلفن همراه را مصرف می کند.با ذخیره سازی درخواست های وب می توانید این مشکل را برطرف کنید ، اما این یک مشکل اساسی جدید ایجاد می کند: اگر داده های کاربر مشابه از نوع دیگری از درخواست ، مانند واکشی لیست دوستان ، نشان داده شود ، چه اتفاقی می افتد؟ این برنامه داده های متناقضی را نشان می دهد که در بهترین حالت گیج کننده است. به عنوان مثال ، اگر کاربر در زمان های مختلف درخواست لیست دوستان و درخواست تک کاربر را ارائه دهد ، ممکن است برنامه ما دو نسخه مختلف از داده های یک کاربر را نشان دهد. برنامه ما باید بفهمد چگونه این داده های ناسازگار را ادغام می کند.روش مناسب برای کنترل این وضعیت استفاده از یک پایگاه داده است. این جایی است که کتابخانه روم به کمک می آید.تعریف: room یک کتابخانه نقشه برداری شی است که ماندگاری داده های محلی را با حداقل کدنویسی در زمان کامپایل فراهم می کند . برخی از جزئیات پیاده سازی اساسی کار با جداول و پرس و جوهای SQL خام را خلاصه می کند. همچنین به شما امکان می دهد تغییراتی را در داده های پایگاه داده ، از جمله مجموعه ها و پیوستن به نمایش داده ها مشاهده کنید ، و با استفاده از اشیا LiveData چنین تغییراتی را نشان می دهید. حتی به صراحت محدودیت های اجرایی را تعریف می کند که به موضوعات متصل به نخ متصل می شوند ، مانند دسترسی به ذخیره سازی در موضوع اصلی.توجه: اگر برنامه شما از قبل از دیتابیس دیگری مانند SQLite نقشه برداری رابطه ای شی (ORM) استفاده کرده است ، نیازی نیست که محلول موجود خود را با Room جایگزین کنید . با این حال ، اگر در حال نوشتن یک برنامه جدید هستید یا در حال ساخت مجدد یک برنامه موجود هستید ، توصیه می کنیم از Room برای ادامه داده های برنامه خود استفاده کنید. به این ترتیب می توانید از قابلیت انتزاع کتابخانه و اعتبار سنجی استفاده کنید.برای استفاده از Room، باید طرحواره محلی خود را تعریف کنیم. ابتدا @Entityحاشیه نویسی را به Userکلاس مدل داده خود و یک @PrimaryKeyحاشیه را به قسمت ایدی کلاس اضافه میکنیم. این حاشیه نویسی به عنوان یک جدول User در پایگاه داده ما و idبه عنوان کلید اصلی جدول علامت گذاری می شود :User@Entity&#xA; data class User(&#xA; @PrimaryKey private val id: String,&#xA; private val name: String,&#xA; private val lastName: String&#xA;)سپس ، با پیاده سازی RoomDatabaseبرای برنامه خود ، کلاس پایگاه داده ایجاد می کنیم:UserDatabase@Database(entities = [User::class], version = 1)&#xA; abstract class UserDatabase : RoomDatabase()توجه داشته باشید که UserDatabaseانتزاعی است. روم به طور خودکار اجرای آن را فراهم می کند.  .اکنون به روشی برای درج داده های کاربر در پایگاه داده نیاز داریم. برای این کار ، ما یک شی دسترسی به داده (DAO) ایجاد می کنیم .UserDao@Dao&#xA; interface UserDao {@Insert(onConflict = REPLACE)&#xA; fun save(user: User)&#xA; &#xA; @Query(&#34;SELECT * FROM user WHERE id = :userId&#34;)&#xA; fun load(userId: String): LiveData&lt;User&gt;&#xA;}روم می داند که چه زمانی پایگاه داده اصلاح می شود و هنگام تغییر داده ها ، به طور خودکار همه مشاهده کنندگان فعال را مطلع می کند. از آنجا که Room از LiveData استفاده می کند ، این عملیات کارآمد است. این داده ها را فقط در صورت وجود حداقل یک ناظر فعال به روز می کند.توجه: روم نامعتبرها را براساس تغییرات جدول بررسی می کند ، به این معنی که ممکن است اعلان های مثبت نادرست ارسال شود.با تعریف کلاسUserDao خود ، ما DAO را از کلاس پایگاه داده خود ارجاع می دهیم:UserDatabase@Database(entities = [User::class], version = 1)&#xA; abstract class UserDatabase : RoomDatabase() {&#xA; abstract fun userDao(): UserDao&#xA;}اکنون می توانیم Repositoryخود را تغییر دهیم تا روم را در آن بگنجانیم:// Informs Dagger that this class should be constructed only once.&#xA; @Singleton&#xA; class UserRepository @Inject constructor(&#xA; private val webservice: Webservice,&#xA; // Simple in-memory cache. Details omitted for brevity.&#xA; private val executor: Executor,&#xA; private val userDao: UserDao&#xA;) {&#xA; fun getUser(userId: String): LiveData&lt;User&gt; {&#xA;       refreshUser(userId)&#xA; // Returns a LiveData object directly from the database.&#xA; return userDao.load(userId)&#xA;   }&#xA; &#xA; private fun refreshUser(userId: String) {&#xA; // Runs in a background thread.&#xA;       executor.execute {&#xA; // Check if user data was fetched recently.&#xA; valuserExists = userDao.hasUser(FRESH_TIMEOUT)&#xA; if(!userExists) {&#xA; // Refreshes the data.&#xA; valresponse = webservice.getUser(userId).execute()&#xA; &#xA; // Check for errors here.&#xA; &#xA; // Updates the database. The LiveData object automatically&#xA; // refreshes, so we don&#39;t need to do anything else here.&#xA;               userDao.save(response.body()!!)&#xA;           }&#xA;       }&#xA;   }&#xA; &#xA; companion object {&#xA; val FRESH_TIMEOUT = TimeUnit.DAYS.toMillis(1)&#xA;   }&#xA;}توجه داشته باشید که حتی اگر مکان ورود اطلاعات UserRepository را تغییر دهیم ، نیازی به تغییر UserProfileViewModelیا تغییر داده خود نداریم . این بروزرسانی محدود ، انعطاف پذیری معماری برنامه ما را نشان می دهد. همچنین برای تست عالی است ، زیرا ما می توانیم  UserRepository جعلی تهیه کنیم و همزمان  UserProfileViewModel خود را آزمایش کنیم .اگر کاربران قبل از بازگشت به برنامه ای که از این معماری استفاده می کند ، چند روز صبر کنند ، به احتمال زیاد اطلاعات خارج از تاریخ را مشاهده می کنند تا زمانی که مخزن بتواند اطلاعات به روز شده را واکشی کند. بسته به مورد استفاده ، ممکن است نخواهید این اطلاعات قدیمی را نشان دهید. در عوض ، می توانید داده های جایگزین یا place holder را نمایش دهید ، که مقادیر مثالی را نشان می دهد و نشان می دهد برنامه شما در حال واکشی اطلاعات بارگیری شده و در حال بارگیری است.منبع واحد حقیقتمعمولاً برای نقاط مختلف REST API بازگشت داده های مشابه است. به عنوان مثال ، اگر backend ما دارای نقطه پایانی دیگری باشد که لیستی از دوستان را برمی گرداند ، همان شی user کاربر می تواند از دو نقطه انتهایی API متفاوت باشد ، حتی ممکن است از سطوح مختلف دانه بندی استفاده کند. اگر بخواهیم همانطور که هست UserRepositoryپاسخ را از روی Webserviceدرخواست برگردانیم ، بدون بررسی سازگاری ، رابط کاربر ما می تواند اطلاعات گیج کننده ای را نشان دهد زیرا نسخه و قالب داده ها از مخزن به نقطه پایانی که اخیراً فراخوانی شده بستگی دارد.به همین دلیل ، UserRepositoryپیاده سازی ما پاسخ های وب سرویس را در پایگاه داده ذخیره می کند. تغییرات موجود در پایگاه داده باعث بازگشت تماس بر روی اشیا فعال  LiveData می شود. با استفاده از این مدل ، پایگاه داده به عنوان منبع واحد حقیقت عمل می کند و سایر قسمتهای برنامه با استفاده از ما به آن دسترسی پیدا می کنند UserRepository. صرف نظر از اینکه از کش دیسک استفاده می کنید ، توصیه می کنیم مخزن خود یک منبع داده را به عنوان منبع واقعی حقیقت برای بقیه برنامه خود تعیین کنید.نمایش عملیات در حال انجامدر برخی یوزکیس ها ، مانند کشیدن برای تازه کردن ، برای UI مهم است که به کاربر نشان دهد در حال حاضر یک عملیات شبکه در حال انجام است. این عملکرد خوبی است که عملکرد UI را از داده های واقعی جدا کنید زیرا ممکن است داده ها به دلایل مختلف به روز شوند. به عنوان مثال ، اگر لیستی از دوستان را واکشی کنیم ، ممکن است همان کاربر مجدداً به صورت خودکار واکشی شود و باعث بروزرسانی LiveData&lt;User&gt; شود. از دید UI ، این واقعیت که یک درخواست در پرواز وجود دارد ، یک نقطه داده دیگر است ، شبیه به هر داده دیگر در خود شی User.● یک تابع عمومی دیگر در UserRepository ارائه دهید که بتواند وضعیت تازه سازی Userرا بازگرداند . این گزینه بهتر است اگر بخواهید وضعیت شبکه را در رابط کاربری خود نشان دهید فقط زمانی که فرآیند واکشی داده از یک اقدام آشکار کاربر مانند کشش برای تازه کردن نشات گرفته باشد. .بهترین روش هابرنامه نویسی یک زمینه خلاقانه است و ساخت برنامه های اندروید نیز از این قاعده مستثنی نیست. راه های زیادی برای حل یک مشکل وجود دارد ، اعم از برقراری ارتباط داده ها بین اکتیویتی های متعدد یا فرگمنت ها ، بازیابی داده های از راه دور و ادامه آن به صورت محلی برای حالت آفلاین ، یا هر تعداد سناریوی رایج دیگری که برنامه های غیرمتعارف با آن روبرو می شوند.اگرچه توصیه های زیر اجباری نیستند ، اما تجربه ما این است که پایه کد شما در طولانی مدت قوی تر ، قابل آزمایش و نگهداری می شود:از تعیین نقاط ورود برنامه خود – مانند اکتیویتی ، سرویس ، ریسیور - به عنوان منابع داده خودداری کنید.در عوض ، آنها فقط باید با سایر مولفه ها برای بازیابی زیرمجموعه داده های مربوط به آن نقطه ورود هماهنگ شوند. هر یک از مولفه های برنامه بسته به تعامل کاربر با دستگاه خود و سلامت کلی سیستم نسبتاً کوتاه مدت است.مرزهای مسئولیت ثابت و مشخصی بین ماژول های مختلف برنامه خود ایجاد کنید.به عنوان مثال ، کدی که داده ها را از شبکه لود می کند در چندین کلاس یا بسته در کد خود پخش نکنید. به همین ترتیب ، چندین مسئولیت غیر مرتبط - مانند ذخیره داده و اتصال داده - در یک کلاس تعریف نکنید.از هر ماژول کمترین حد ممکن را نشان دهید.وسوسه نشوید که میانبر &#34;فقط آن یک&#34; ایجاد کنید که جزئیات اجرای داخلی را از یک ماژول نشان دهد. ممکن است در کوتاه مدت کمی وقت خود را بدست آورید ، اما بعداً با تکوین پایگاه کد خود چندین برابر بدهی فنی می گیرید.در نظر بگیرید که چگونه هر ماژول را به صورت جداگانه آزمایش کنیم.به عنوان مثال ، داشتن یک APIکاملاً مشخص برای واکشی داده ها از شبکه ، آزمایش ماژولی را که ادامه داده را در پایگاه داده محلی آسان می کند ، آسان می کند. در عوض ، اگر منطق این دو ماژول را در یک مکان مخلوط کنید یا کد شبکه خود را در کل کد خود پخش کنید ، آزمایش آن بسیار دشوارتر می شود - اگر غیرممکن نباشد.بر هسته منحصر به فرد برنامه خود تمرکز کنید تا از سایر برنامه ها متمایز شود.چرخ را با نوشتن مجدد همان کد دیگ بخار دوباره اختراع نکنید. درعوض ، وقت و انرژی خود را بر روی آنچه منحصر به فرد برنامه شماست متمرکز کنید و به اجزای معماری Androidو سایر کتابخانه های توصیه شده اجازه دهید دیگ بخار تکراری را کنترل کنند.تا آنجا که ممکن است داده های مرتبط و تازه را ادامه دهید.به این ترتیب ، حتی اگر دستگاه آنها در حالت آفلاین باشد ، کاربران می توانند از عملکرد برنامه شما لذت ببرند. به یاد داشته باشید که همه کاربران شما از اتصال ثابت و پرسرعت برخوردار نیستند.یک منبع داده را به عنوان منبع واحد حقیقت اختصاص دهید.هر زمان که برنامه شما نیاز به دسترسی به این داده داشته باشد ، همیشه باید از همین منبع حقیقت سرچشمه بگیرد .آخرین به روزرسانی: 08/02/2020https://developer.android.com/reference/android/arch/lifecycle/LiveData</description><category>حمید امیرحاجلو</category><author>حمید امیرحاجلو</author><pubDate>Thu, 08 Oct 2020 20:45:47 +0330</pubDate></item></channel></rss>