<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Special N9NE</title>
        <link>https://virgool.io/feed/@Special_N9NE</link>
        <description>اندروید دولوپر ؟ بله.</description>
        <language>fa</language>
        <pubDate>2026-06-16 08:24:30</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/84350/avatar/GoCYYt.jpeg?height=120&amp;width=120</url>
            <title>Special N9NE</title>
            <link>https://virgool.io/@Special_N9NE</link>
        </image>

                    <item>
                <title>آموزش Work Manager در اندروید 1 : مقدمه</title>
                <link>https://virgool.io/@Special_N9NE/workmanager1-zkomzjvu2tnm</link>
                <description>سلام. خوش اومدید😊اینجا قسمت اول اموزش Work Manager توی اندروید هستش که توی این قسمت به معرفی، معماری و یه مثال ساده از Work Manager میپردازیم. خب بریم ببینیم چه خبره..ا Work Manager چیه و به چه دردی میخوره؟گاهی اوقات ممکنه به سناریو هایی توی اپ بخوریم که نیازه یه کاری در پس زمینه جدا از Life Cycle اپ انجام بشه. یعنی حتی اگه اپ بسته شد تسک ما انجام بشه. برای همچین مواقعی اندروید Work Manager رو معرفی کرده. Work Manager بخشی از ابزار های Jetpack هستش که برای اولین بار توی کنفرانس Google I/O در سال 2018 معرفی شد.در واقع Work Manager برای تسک هایی ساخته شده که شما نیاز دارید کاری در Background Thread انجام بشه و این انجام شدنه تضمینی و قطعی باشه. برای مثالش میتونیم sync کردن اطلاعات با دیتابیس، آپلود/دانلود فایل ها، ارسال نوتیفیکیشن و غیره رو مثال بزنیم و همچنین میتونید این تسک رو بهش زمان اجرا بدید که در اون زمان اجرا بشه (به صورت تقریبی).پس میشه گفت:به صورت کلی Work Manager ابزاریه که میشه با اون یه سری تسک تعریف کرد که اون تسک به صورت تقریبی در زمان تعیین شده و در صورت وجود شرایط تعیین شده در بک گراند و خارج از اپ اجرا بشه.شاید سوال بشه در اینجا منظورمون از شرایط تعیین شده چیه. ببینید توی Work Manager میتونید یه سری Constraint تعریف کنید . Constraint یعنی شرایط خاصی که میخوایم تسک ما تنها زمانی که این شرایط مهیاست اجرا بشه، برای نمونه این تسک مثلا فقط وقتی که گوشی توی شارژه یا حافظه کافی داره یا اینترنتش روشنه و ... اجرا بشه. وقتی موقع اجرای کد های شما فرا برسه اول این Constraint ها توسط Work Manager چک میشه، اگه برقرار بود که خب کدتون اجرا میشه اما در غیر این صورت Work Manager منتظر میمونه تا تمامی Constraint هایی که معین کردید برقرار بشه و بعد اجرا میکنه.مثال ساده از Work Managerبیاید توی کد با Work Manager اشنا شیم.خب قدم صفر اینه که Work Manager رو به پروژه اضافه کنیم. پس این تیکه کد رو باید توی build.gradle بذارید..dependencies {
    val work_version = &amp;quot2.9.1&amp;quot
    implementation(&amp;quotandroidx.work:work-runtime-ktx:$work_version&amp;quot)
}حالا اول از همه باید یه کلاس بسازیم که کدای اون تسک ما (که از این به بعد بهش میگیم Worker) رو توش بنویسیم.class UploadWorker(appContext: Context, workerParams: WorkerParameters):
       Worker(appContext, workerParams) {
   override fun doWork(): Result {

       uploadImages()

       return Result.success()
   }
}خب همونطور که میبینید من یه کلاس UploadWorker ساختم که از Worker ارث بری میکنه. کلاس Worker یه تابع داره که باید override بشه به اسم doWork . توی این تابع ما عملیاتی که میخوایم این Worker انجام بده رو مینویسیم.خب میبینید که این تابع ازمون میخواد که یه کلاس Result برگردونیم. Result نتیجه Worker ماست که 3 تا حالت داره : Success , Failed , Retry.وقتی نتیجه رو Retry برمیگردونید این Worker یه بار دیگه وقتی که شرایط مهیا بود اجرا میشه. البته به صورت پیشفرض بعد از سه بار Retry کردن Worker شما خودکار Failed میشه و دیگه اجرا نمیشه.خب حالا که Worker رو ساختیم بریم ازش استفاده کنیم. val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder&lt;UploadWorker&gt;()
       .build()

WorkManager
    .getInstance(myContext)
    .enqueue(uploadWorkRequest)این نمونه کدی هستش که توی ViewModel باید باشه. بریم بخش بخش بررسیش کنیم.خب اول از همه یه WorkRequest داریم تعریف میکنیم. WorkRequest ها دو نوع دارن OneTimeWorkRequest و PeriodicWorkRequest. توی PeriodicWorkRequest شما تعریف میکنید که Worker من به صورت دوره ای مثلا هر ساعت یک بار باید اجرا بشه اما توی  OneTimeWorkRequest شما میگید که Worker من نیاز به یک بار اجرا داره و بعد از Success یا Failed شدن کارش تموم میشه.که خب برای UploadWorker ما فقط نیاز داریم که Worker یه بار کارشو انجام بده پس از OneTimeWorkRequest استفاده میکنیم و به عنوان ورودی Worker ای که ساختیم رو بهش میدیم و Build میکنیمش. قبل Build میتونستیم یه تنظیماتی به ریکوئستمون بدیم مثل همون Constraint هایی که گفتم، ولی فعلا بدون هیچ چیز اضافه ای کارو پیش میبریم.حالا با استفاده از کلاس WorkManager (که یه Singleton هستش) بعد از اینکه Context رو بهش دادیم درخواست اجرای WorkRequest رو با استفاده از enqueue ثبت میکنیم.معماری Work Managerخب بیایم ببینیم حالا چه اتفاقی میوفته. الان که ما یه Work رو ثبت کردیم، سیستم منابع خودش رو (مثلا چقد رم و CPU درگیره) رو حساب میکنه. اگه شرایط اوکی بود که خب در همون لحظه Worker ما اجرا میشه اما اگه کمبودی توی منابع باشه یا پردازش های دیگه ای توی الویت باشن، سیستم Worker مارو نگه میداره تا وقتی که شرایط به نرمال برسه و بعد اجرا میکنه. برای همینه که اصولا شما وقتی با Work Manager کار میکنید نباید توقع اجرای درلحظه Worker خودتون باشید و همیشه باید درنظر بگیرید مقداری تاخیر در اجرا وجود داره.سیستم Work Manager اینجوری کار میکنهمعماری Work Managerشما میایید یه ریکوئست رو enqueue میکنید. بخش Internal TaskExecutor میاد اونو توی دیتابیس Work Manager (که همه ریکوئست های اپ ها توش ذخیره شده) سیو میکنه. حالا وقتی که شرایط مهیا شد (زمان اجرای شما فرا رسید - Constraint ها برقرار بود - منابع سیستم مناسب بود) Internal TaskExecutor به کلاس WorkerFactory میگه که Worker مربوط به شما رو بسازه و متد doWork اونو صدا بزنه که درنهایت به اجرای کد های شما ختم میشه.خب تا همینجا قسمت اول آموزش Work Manager تموم میشه. توی قسمت بعدی به مقایسش با ابزار های دیگه میپردازیم.اگه پست رو دوست داشتید حتما لایک کنید. برای حمایت بیشتر از من هم میتونید از اینجا coffeebede.com/n9ne یه قهوه مهمونم کنید که بهترین حمایت ممکنه ❤️</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Thu, 07 Nov 2024 14:21:20 +0330</pubDate>
            </item>
                    <item>
                <title>ایمن سازی کلید های API و توکن ها در اندروید استودیو</title>
                <link>https://virgool.io/@Special_N9NE/api-key-security-in-android-jlxrdruz4wuc</link>
                <description>سلام، بعد یه مدت تاخیر این دفعه با یه پست در مورد امنیت توی اندروید اومدم.😉🔒 داستان از این قراره که اگه در مورد اینکه توی پروژتون کلید های حساس مثل API رو کجا بذارید که امن باشه تحقیق کنید، میبینید که اکثر منابع روش ذخیره سازی کلید ها توی فایل gradle.properties یا local.properties رو یاد میدن بهتون. ولی روش پیشنهادی اینه که بیاید فایل properties. مخصوص خودتونو بسازید.فایل local.properties یه فایل رزرو شده برای اطلاعات مربوط به AGP (android gradle plugin) هستش و تغییر دادنش ممکنه مشکلاتی رو براتون ایجاد کنهوقتی شما این فایل رو باز کنید هم میبینید که بهتون هشدار داره این فایل رو تغییر ندید چون تغییرات شما پاک میشن به صورت خودکار.از طرف دیگه فایل gradle.properties برای اطلاعات gradle در سطح پروژه هست و گاهی ممکنه توی ورژن کنترلتون در کنار سورس کد پروژه لحاظ بشه.این فایل اصولا جای مناسبی برای ذخیره اطلاعات حساس مثل کلید هاتون نیست ، مخصوصا وقتی دارید پروژتون رو به اشتراک میگذارید.خب حالا برای اینکه فایل properties. خودتونو بسازید باید مراحل زیر رو یکی یکی انجام بدید:قدم اول : روی پوشه اصلی پروژتون فایل apikeys.properties رو بسازید. البته میتونید از اسم دلخواه استفاده کنید فقط مهمه تهش properties. باشه. بعدش داخلش میتونید یه همچین چیزی رو بنویسید:#put your api key in the newly created .properties file
API_KEY = &amp;quotabcd1234&amp;quot
(برای اینکه فایل رو توی پوشه درستی ایجاد کنید نمایش پوشه هارو بزارید روی حالت project نه android)قدم دوم : حالا باید این فایلی که ساختید رو به gitignore اضافه کنید تا شامل گیت نشه. حواستونم باشه وقتی داشتید فایلو میساختید اگر پیغام افزودن فایل به گیت اومد نباید accept میکردید.*.iml
.gradle
/local.properties
/.idea/caches
....
#put your .properties file here
  apikeys.propertiesقدم سوم : توی فایل build.gradle میتونید اطلاعاتی که توی فایل properties. داشتید روی لود کنید که بعدا توی کداتون قابل استفاده باشه:android { 
    ...

 defaultConfig {
     ...

 //load the values from .properties file
 val keystoreFile = project.rootProject.file&#40;&amp;quotapikeys.properties&amp;quot&#41;
 val properties = Properties()
      properties.load(keystoreFile.inputStream())
 
 //return empty key in case something goes wrong
 val apiKey = properties.getProperty(&amp;quotAPI_KEY&amp;quot) ?: &amp;quot&amp;quot
قدم چهارم : توی اندروید یه کلاسی به اسم BuildConfig وجود داره که به صورت خودکار توسط پلاگین gradle در فرایند build ایجاد میشه و شامل اطلاعات مختلفی هستش که توی کداتون میتونید ازش استفاده کنید. اطلاعات درون فایل properties. ما هم با استفاده از این کلاس قابل دسترسیه ، منتها برای اینکه این دسترسی ایجاد بشه باید در ادامه کد های قبلی، این تیکه کد رو هم اضافه کنید://this is from the above previous code
val apiKey = properties.getProperty(&amp;quotAPI_KEY&amp;quot) ?: &amp;quot&amp;quot
        buildConfigField(
                type = &amp;quotString&amp;quot,
                name = &amp;quotAPI_KEY&amp;quot,
                value = apiKey
        )تابع buildConfigField برای تزریق کردن اطلاعات مختلف مثل کلید Api به کلاس BuildConfig استفاده میشه.این تابع 3 تا ورودی میگیره:ا type : نوع مقدارتون که توی مثال ما یه String هستشا name : اسم متغیری که توی فایل properties. دادیدا value : مقدار متغیر که خب برای ما همون کلید API هست که تونستیم توی کد های قبل بدستش بیاریم.این باعث میشه توی کلاس BuildConfig یه متغیر به اسم API_KEY با نوع String ساخته بشه با مقداری که توی فایل properties. هست.قدم پنجم : با استفاده از کلاس BuildConfig به کلیدتون دسترسی پیدا کنید. کافیه کد زیر رو بنویسید:class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //call BuildConfig to access its API_KEY Constant
        val apiKey = BuildConfig.API_KEY
        Log.i(&amp;quottag&amp;quot, apiKey)
   }
}با این روش وقتی شما کداتونو به اشتراک میگذارید توی گیتهاب ، مغادیر مهمتون که داخل فایل properties. تعریفشون کردید در دسترس بقیه قرار نمیگیره.امیدوارم بدردتون خورده باشه این آموزش، اگه اینطوره میتونید با لایک کردنش حمایتتونو نشون بدید ⭐اگرم دوست دارید مبحث دیگه ای رو توضیح بدم حتما برام کامنتش کنید 🙏</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Sat, 03 Aug 2024 23:07:56 +0330</pubDate>
            </item>
                    <item>
                <title>ذخیره اطلاعات با DataStore در اندروید</title>
                <link>https://virgool.io/codenevis/datastore-introduction-vj1dlues6w1r</link>
                <description>توی سال 2021 ورژن پایدار از DataStore منتشر شد. این کتابخونه که جزو کتابخونه های Jetpack هم هست به عنوان راهی جدید برای ذخیره اطلاعات کوچک و جایگزینی برای SharedPreferences معرفی شد.(نکته: DataStore دو نوع داره : Proto و Preferences . این پست روی پیاده سازی Preferences تمرکز میکنه)خب اول از همه باید ببینیم چرا نیازه ازش استفاده کنیم . مزایایی که قراره بررسیشون کنیم اینا هستن:اجرا به صورت asynchronousسازگاری اطلاعاتکنترل خطا هاانتقال راحت از SharedPreferencesا Asynchronousیکی از معایب اصلی SharedPreferences که اتفاقا نقطه قوت DataStore هست اینکه که SharedPreferences عملیاتش رو روی Main Thread اجرا میکنه ، در نتیجه باعث میشه رابط کاربری ما هنگام استفاده ازش بلاک بشه. اما از اون طرف این قضیه برای DataStore کاملا فرق میکنه چون عملیاتش رو با استفاده از قدرت Kotlin Coroutines و Flow به صورت نامتقارن (asynchronous) و در یه Thread جدا (به صورت پیشفرض IO ، البته میتونید تغییرش بدید) اجرا میکنه که باعث میشه رابط کاربری متوقف نشه.ا Data Consistencyیه مسئله دیگه ای که وجود داره Data Consistency یا سازگاری اطلاعات هستش. توی SharedPreferences چون عملیات به صورت متقارن (synchronous) انجام میشه برای مثال وقتی یه برنامه داریم که چند تا  Thread مختلف داره ، ممکنه اطلاعاتمون به درستی ذخیره نشه پس ممکنه اخرین چیزی که ذخیره شده باشه چیزی نباشه که ما انتظار داریم باشه.حالا DataStore اومده این مشکل رو حل کرده. با استفاده از این میتونید همیشه مطمئن باشید که اطلاعاتی که الان دارید دریافت میکنید اخرین اطلاعاتی هست که ذخیره کردید.ا Error Handlingنقطه تفاوت بعدی کنترل خطا (Error handling) هستش. توی SharedPreferences شما احتمال برخورد با کرش رو دارید ولی با استفاده از DataStore که از Flow استفاده میکنه میتونید خطا هایی که پیش میاد رو مدیریت کنید.ا Easy Migrationعلاوه بر همه مزایا که گفته شد، انتقال اطلاعات از SharedPreferences به DataStore خیلی راحته و به آسونی میتونید شروع به استفاده از DataStore بکنید.پیاده سازیخب برای پیاده سازی DataStore اول از همه باید Dependency اش رو نصب کنیم. پس توی فایل app/build.gradle در قسمت dependencies این خط رو اضافه میکنیم:implementation(&amp;quotandroidx.datastore:datastore-preferences:1.0.0&amp;quot)حالا این فایلو میسازیم:object DataStoreKeys {

    private val dataStore = stringPreferencesKey(&amp;quotapp&amp;quot)
    val username = stringPreferencesKey(&amp;quotusername&amp;quot)

    val Context.dataStore by preferencesDataStore (name = dataStore.name)
}توی این فایل اومدیم کلید های DataStore تعریف میکردیم و بعدش هم با استفاده از preferencesDataStore خود DataStore رو ساختیم. و حالا همه چی برای ما آماده استفاده کردنه.برای خوندن اطلاعات میشه یه همچین چیزی توی activity نوشت:private suspend fun getName(): String? {
    val ps = this.dataStore.data.first().toPreferences()
    return ps[username]
}(دقت کنید this داره به Context اکتیویتی اشاره میکنه)اینجوری هم میشه اطلاعات رو سیو کرد:private suspend fun saveName(value: String) {
    this.dataStore.edit {
        it[username] = value
    }
}از اونجایی که این دوتا تابع ما suspend هستن پس باید توی Coroutine Scope اجرا بشن. برای اینکار میشه از lifecycleScope استفاده کرد:lifecycleScope.launch {
    saveName(&amp;quotAmir&amp;quot)
    getName()
}به همین روال میتونید DataStore خودتونو بسازید و ازش استفاده کنید.ا Migrationاگه توی کداتون از SharedPreferences استفاده میکردید و حالا میخواید همه اطلاعات رو منتقل کنید به DataStore و از این به بعد از DataStore استفاده کنید، کافیه این تغییرو رو توی فایل  DataStoreKeys که ساختیمش بدید:object DataStoreKeys {
    private val dataStore = stringPreferencesKey(&amp;quotapp&amp;quot)
    val username = stringPreferencesKey(&amp;quotusername&amp;quot)
    val Context.dataStore by preferencesDataStore(
        name = dataStore.name,        //add this line
        produceMigrations = { context -&gt;
            listOf(SharedPreferencesMigration(context, &amp;quotYOUR_SHARED_PREFERENCES_NAME&amp;quot))
        }
    )
}فقط کافیه بجای YOUR_SHARED_PREFERENCES_NAME اسم SharedPreferences خودتونو بدید و تموم. به همین راحتی همه اطلاعات به همراه کلید هاشون به DataStore منتقل شدن.اگه دوست داشتید خوشحال میشم لایک کنید و نظراتتونو کامنت کنید. اگر هم آموزش چیز دیگه ای رو میخواید برای پست بعدی حتما بهم بگید.اپ جدیدمون هم منتشر کردیم به تازگی، میتونید از اینجا ببینیدش. حمایت هاتون خیلی با ارزشه❤️</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Thu, 11 Apr 2024 23:40:04 +0330</pubDate>
            </item>
                    <item>
                <title>معرفی توابع پرکاربرد در کاتلین</title>
                <link>https://virgool.io/@Special_N9NE/kotlin-extension-functions-t5z7jc6txfze</link>
                <description>کاتلین یه زبون پرقدرته که توابع زیادی هم داره اما یکی از اصلی ترین نقاط قوت کاتلین extension function ها هستن. با استفاده از این توابع شما میتونید به کلاس ها و آبجکت هایی که در کاتیلن وجود دارن قابلیت هایی رو اضافه کنید بدون اینکه تغییری توی سورس کد اون ها ایجاد بشه.توی این پست ما چند تا از تابع های خیلی ساده که کد هاتونو خواناتر و کدنویسی رو براتون آسون تر میکنه رو بررسی میکنیم و با این نوع تابع ها آشنا میشیم.https://unsplash.com/photos/azJJSiwWW90?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditShareLink ا withNotNullیکی از مسائل پرکاربردی که برنامه نویسا باهاش سروکار دارن اینه که یه عملیاتی رو اگه یه متغیری Null نیست انجام بدن. این تابع کار رو اسون تر میکنه و فقط وقتی کدها اجرا میشه که متغیر Null نباشه.inline fun &lt;T : Any, R&gt; T?.withNotNull(block: (T) -&gt; R): R? {
 return this?.let(block)
}نحوه استفاده : val nullableValue: String? = null
nullableValue.withNotNull { value -&gt;
 // کد های اینجا زمانی اجرا میشه که متغیر نال نباشه
}ا toLiveDataبا این تابع میتونید یه Flow رو به LiveData تبدیل کنیدfun &lt;T&gt; Flow&lt;T&gt;.toLiveData(): LiveData&lt;T&gt; {
 return liveData {
        collect {
            emit(it)
        }
    }
}نحوه استفاده:val flow = flowOf(&amp;quotHello&amp;quot, &amp;quotWorld&amp;quot)
val liveData = flow.toLiveData()ا toFormattedStringاگه میخواین عدد هاتونو یا یه تاریخ رو فرمت کنید میتونید از این تابع استفاده کنیدfun Int.toFormattedString(): String {
 return NumberFormat.getInstance().format(this)
}

fun Long.toFormattedString(): String {
 return NumberFormat.getInstance().format(this)
}

fun Date.toFormattedString(): String {
 return SimpleDateFormat.getDateInstance().format(this)
}نحوه استفاده:val number = 1000000
val formattedNumber = number.toFormattedString()ا debounceبا این تابع میتونید کاری کنید که عملیاتی که میخواید انجام بشه فقط در صورت اجرا بشه که مدت زمان معینی از اخرین اجراش گذشته باشه. برای مثال فقط درصورتی کد های مربوط به کلیک دکمه اجرا بشه که از اخرین کلیک 500 میلی ثانیه گذشته باشه.fun View.onClick(debounceDuration: Long = 300L, action: (View) -&gt; Unit) {
    setOnClickListener(DebouncedOnClickListener(debounceDuration) {
        action(it)
    })
}

private class DebouncedOnClickListener(
 private val debounceDuration: Long,
 private val clickAction: (View) -&gt; Unit
) : View.OnClickListener {

 private var lastClickTime: Long = 0

 override fun onClick(v: View) {
 val now = SystemClock.elapsedRealtime()
 if (now - lastClickTime &gt;= debounceDuration) {
            lastClickTime = now
            clickAction(v)
        }
    }
}نحوه استفاده:button.onClick(debounceDuration = 500L) { // کد زمانی اجرا میشه که از اخرین کلیک 500 میلی ثانیه گذشته باشه}ا toBitmapاگه میخواین یه Drawable رو به Bitmap تبدیل کنید این تابع به کمکتون میاد.fun Drawable.toBitmap(): Bitmap {
 if (this is BitmapDrawable) {
 return bitmap
    }

 val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
 val canvas = Canvas(bitmap)
    setBounds(0, 0, canvas.width, canvas.height)
    draw(canvas)

 return bitmap
}نحوه استفاده:val drawable = ContextCompat.getDrawable(context, R.drawable.my_drawable)
val bitmap = drawable.toBitmap()ا applyIfبا این تابع کد های مربوط به یه آبجکت فقط زمانی اعمال میشن که شرط ما درست باشهinline fun &lt;T&gt; T.applyIf(condition: Boolean, block: T.() -&gt; Unit): T {
    return if (condition) {
              this.apply(block)
    } else {
      this
    }
}نحوه استفاده:val number = 5
val formattedNumber = number.applyIf(number &gt; 10) {
    toFormattedString()
}به طور خلاصه با کاتلین میتونید تعداد زیادی از این جور چیزا رو بنویسید و توی زمانتون صرفه جویی و کد هاتونو هم خواناتر کنید.این پست از اینجا ترجمه شده. اگه ترجمه های دیگه ای در مورد اندروید میخواید میتونید برام اینجا کامنت کنید. اگه خوشتون اومد لایک یادتون نره و همچنین میتونید با استفاده از این لینک برام یه قهوه بخرید :) ❤️ بدرود .</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Tue, 01 Aug 2023 17:44:01 +0330</pubDate>
            </item>
                    <item>
                <title>آموزش GraphicsLayer Modifier در jetpack compose اندروید</title>
                <link>https://virgool.io/@Special_N9NE/graphics-layer-modifier-jetpack-compose-r6idnd9zxate</link>
                <description>تا حالا براتون سوال شده چجوری میشه توی اپلیکیشنتون انیمیشن هایی مثل انیمیشنی که توی اپ Wordle هست پیاده سازی کنید؟ توی این پست با استفاده از Jetpack Compose و graphicsLayer modifier بهتون میگم چجوری میشه.اگه این براتون جذاب و هیجان انگیز نبود ، توی مثال پایینی نشون میدم چجوری یه برگه کارت پاسور رو در جهات مختلف بچرخونیم و انیمیشن های دیگه بهش بدیم. این هم با استفاده از موارد قبلی.میتونید کد هاشو از اینجا ببینید.در این پست در مورد اینا حرف میزنیم :    - این graphicsLayer چیا داره و به چه دردی میخوره    - چجوری یه کارت دو طرفه بسازیم که توی هر دو مثال بالا ازش استفاده شدا  graphicsLayer modifierتوی کد پایین همه پارامتر های graphicsLayer رو لیست کردم:Modifier    .graphicsLayer(        alpha = alpha,        translationX = translationX,        translationY = translationY,        shadowElevation = shadowElevation,        scaleX = scaleX,        scaleY = scaleY,        rotationX = rotationX,        rotationY = rotationY,        rotationZ = rotationZ,        cameraDistance = cameraDistance,        transformOrigin = TransformOrigin(originX, originY),        shape = roundedDegree,        clip = true,        renderEffect = BlurEffect(blur, blur)    )ا  Alphaآلفا  باعث میشه ویو شفاف یا غیر شفاف بشها  Translationپارامتر  translationX ویو رو به سمت چپ و راست میبره و translationY به سمت بالا و پایینا  Shadowبا زیاد کردن این پارامتر سایه دور ویو بیشتر میشها  Scaleمقیاس افقی با scaleX تنظیم میشه و عمودی با scaleYا  Rotationپارامتر rotationX چرخش افقی ، rotationY  چرخش عمودی ، rotationZ چرخش در محور Zتوجه کنید موقع چرخش ، متن NE هم میچرخن وقتی کارت برمیگرده . چون این نتیجه توی ساخت انیمیشن Wordle بدردمون نمیخوره ، باید یه ویو بسازیم که دو طرفه باشه.ا  Originبا استفاده از این نقطه چرخش ویو رو تنظیم میکنیم. به صورت پیشفرض مقادیر (0.5 , 0.5) داره که اگه ما تغییرش بدیم به (0,0) اینجوری میشها  Camera Distanceاین هم یکی دیگه از پارامتراییه که مربوط به چرخشه و بیانگر اینه که دوربین چقد با ویو فاصله داره. هر چی جلو تر باشه ویو موقع چرخش بزرگ تر دیده میشه. مقدار پیشفرض 8.0f هستش.وقتی تغییرش بدیم به 3.0f موقع چرخش حس میکنیم دوربین جلوتره.وقتی هم که بدیم 50.0f انگار دوربین خیلی دوره و ویو صاف و مسطح میچرخه.ا  Shapeبا استفاده از این میشه برای ویو ها شکل درنظر گرفت . نمونه های پایین مثال هایی از شکل ها هستن . البته باید clip رو برابر true قرار بدید تا کار کنه.ویو دو طرفهمن یه composable ساختم که یه ویو دو طرفه میسازه یعنی وقتی میچرخه ، کارت برمیگرده.برای چرخش این کارت فقط کافیه عکس جلو و پشت کارت رو به تابع بدیمDoubleSide(
    rotationX = rotationX,
    rotationY = rotationY,
    rotationZ = rotationZ,
    cameraDistance = 16f,
    flipType = FLIPTYPE.HORIZONTAL,
    front = {
 Image(
 painterResource(R.drawable.card_front),
            contentDescription = &amp;quotPoker Front&amp;quot,
            modifier = Modifier.border(
                2.dp, Color.Black, shape = roundedDegree)
        )
 }, back = {
 Image(
 painterResource(R.drawable.card_back),
            contentDescription = &amp;quotPoker Front&amp;quot,
            modifier = Modifier.border(
                2.dp, Color.Black, shape = roundedDegree)
        )
 })قسمت Composable  هم کداش اینجوریه@Composable
fun DoubleSide(
    translationX: Float = 0f,
    translationY: Float = 0f,
    rotationX: Float = 0f,
    rotationY: Float = 0f,
    rotationZ: Float = 0f,
    cameraDistance: Float = 8f,
    flipType: FLIPTYPE,
    front: @Composable () -&gt; Unit,
    back: @Composable () -&gt; Unit
) {
    fun isHorizontallyFlip() = (abs(rotationX) % 360 &gt; 90f 
            &amp;&amp; abs(rotationX) % 360 &lt; 270f)
    fun isVerticallyFlip() = (abs(rotationY) % 360 &gt; 90f 
            &amp;&amp; abs(rotationY) % 360 &lt; 270f)

    fun isFlipped() = isVerticallyFlip() xor isHorizontallyFlip()

    if (isFlipped()) {
        val rotationXBack =
            if (flipType == FLIPTYPE.HORIZONTAL)
                rotationX - 180
            else
                rotationX

        val rotationYBack =
            if (flipType == FLIPTYPE.VERTICAL)
                rotationY - 180
            else
                -rotationY
 Box(
            Modifier
                .graphicsLayer(
                    translationX = translationX,
                    translationY = translationY,
                    rotationX = rotationXBack,
                    rotationY = rotationYBack,
                    rotationZ = -rotationZ,
                    cameraDistance = cameraDistance
                )
        ) {
 back()
 }
 } else {
 Box(
            Modifier
                .graphicsLayer(
                    translationX = translationX,
                    translationY = translationY,
                    rotationX = rotationX,
                    rotationY = rotationY,
                    rotationZ = rotationZ,
                    cameraDistance = cameraDistance
                )
        ) {
 front()
 }
 }
}برای اینکه تابع DoubleSide بهتری داشته باشیم نیاز به محاسبات بیشتری هست توی قسمت چرخش که توی کارت پاسور به چشم نمیومد، چونکه کارت های پاسور به صورت عمودی متقارن هستن ولی بعضی کارت ها عمودی و افقی نا متقارن هستنبه همین دلیل توی تابع DoubleSide من شما میتونید flipType بدید که یا FLIPTYPE.HORIZONTAL یا FLIPTYPE.VERTICAL .چرخش ویو افقیاینجا پشت کارت به صورت افقی میچرخه.    - اگه افقی بچرخونید ، کلمه ای که پشت کارته رو به شکل درست میبینید    - اگه عمودی بچرخونید ، کلمه پشت کارت برعکس میشهچرخش ویو عمودیاینجا پشت کارت به صورت عمودی میچرخه.    - اگه عمودی بچرخونید ، کلمه ای که پشت کارته رو به شکل درست میبینید    - اگه افقی بچرخونید ، کلمه پشت کارت برعکس میشهپس پارامتر fliptype برای اینکار استفاده میشه.امیدوارم از این modifier خوشتون اومده باشه و یه جذابیت خاصی به اپلیکیشنتون داده باشه.اگه این پست رو دوست داشتید میتونید با استفاده از این لینک ازم حمایت کنید :)</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Fri, 12 May 2023 13:17:07 +0330</pubDate>
            </item>
                    <item>
                <title>مواجه با فرد افسرده: رفتار درست</title>
                <link>https://virgool.io/baraye/%D9%85%D9%88%D8%A7%D8%AC%D9%87-%D8%A8%D8%A7-%D9%81%D8%B1%D8%AF-%D8%A7%D9%81%D8%B3%D8%B1%D8%AF%D9%87-%D8%B1%D9%81%D8%AA%D8%A7%D8%B1-%D8%AF%D8%B1%D8%B3%D8%AA-d9manvtgw87u</link>
                <description> افسردگی یه اختلال جدی ولی قابل درمانه که میلیون ها نفر رو در سراسر جهان ، در تمامی رده های سنی از جوون تا پیر رو دربرمیگیره. افسردگی توی زندگی روزمره نفوذ میکنه و باعث درد و عذاب قابل توجهی میشه، این عذاب نه تنها شامل کسی که افسردگی رو تجربه میکنه میشه بلکه روی افراد اطراف اون شخص تاثیر میزاره.اگه کسی که دوستش دارید دچار افسردگی شده ، شما ممکنه تعداد نامشخصی از احساساتی مثل درماندگی ، ناامیدی ، عصبانیت ، ترس ، احساس گناه و ناراحتی رو تجربه کنید. همه این احساسات نرمال هستن. مواجه شدن با دوست یا عضوی از خانواده که دچار افسردگی شده ساده نیست و اگه شما در حفاظت از سلامت روانی خودتون کم کاری کنید، ممکنه مشکلات جدی ای رو به وجود بیاره.در این راستا ، همراهی و حمایت شما نسبت به فردی که دوستش دارید میتونه خیلی حیاتی باشه برای ریکاوری اون فرد. شما میتونید توی مواردی مثل کنار اومدن با عواقب افسردگی، غلبه بر افکار مخرب، بازیابی انرژی، بهبود کارایی و لذت از بردن از زندگی به فرد مورد نظر کمک کنید. برای شروع در مورد افسردگی و اینکه چجوری باید با یه فرد افسرده حرف بزنید اطلاعات جمع اوری کنید و یاد بگیرید. ولی بعد اینکه این کارا تموم شد، فراموش نکنید که به سلامت روانی خودتون هم برسید تا هم خودتون درگیر بیماری روانی نشید و هم بتونید به بهترین حالت به افراد مورد علاقتون کمک کنید.بعضی وقتا ادم نمیدونه چی باید بگه وقتی میخواد در مورد افسردگی حرف بزنه. شاید شما نگران باشید که اگه از نگرانی هاتون درباره افسردگی اون فرد حرف بزنید، فرد عصبی بشه یا حس کنه که بهش توهین کردید یا اینکه اصلا حرفای شما رو بی اهمیت جلوه بده. شاید شما ندونید که چه سوالی باید بپرسید یا چطور نقش یه حامی و همراه رو بازی کنید.اگه نمیدونید از کجا باید شروع کنید، این پیشنهادات میتونه بهتون کمک کنه ولی حواستون باشه یه شنونده خوب بودن خیلی بیشتر مهمه تا اینکه بیاید نصیحت کنید. لازم نیس شما دوستتون یا عضو خانوادتونو &quot;اصلاح&quot; کنید، فقط کافیه شنونده خوبی باشید. خیلی وقتا یه صحبت رو در روی ساده میتونه کمک بسیار بزرگی برای فرد افسرده باشه. فرد رو تشویق کنید تا حرف بزنه و از احساساتش بگه و بهش بگید که بدون قضاوت کردنش اینجایید که به حرفاش گوش کنید.شروع صحبتهمیشه سخت ترین قسمت شروع کردن صحبت در مورد افسرگی با فرد مورد نظره. شما میتونید با این چیزا شروع کنید: چند وقتیه که در موردت نگرانم چند وقتیه که میبینم تغییر کردی، خواستم ازت بپرسم حالت چطوره میخواستم حالتو بپرسم چون بنظر میاد چند وقتیه ناراحتیوقتی که دارید حرف میزنید ، میتونید اینجور سوالا رو بپرسید: از کی این احساست شروع شد؟ اتفاقی افتاد که باعث شد این احساسات سراغت بیان؟ چجوری میتونم کمکت کنم؟ به کمک گرفتن از بقیه فکر کردی؟حرفایی که میتونید بزنیدتو تنها نیستی. من توی این زمان سخت کنارتم.شاید باورش الان برات سخت باشه ولی این چیزایی که الان داری حس میکنی و تجربه میکنی عوض میشه.لطفا بهم بگو برات کمک بهت چه کاری میتونم بکنم.حتی اگه دقیقا احساستو درک نکنم، بهت اهمیت میدم و میخوام کمکت کنم. تو برای من مهم و با ارزشی. زندگی تو و جوری که زندگی میکنی برام مهمه. هر وقت خواستی تسلیم بشی ، به خودت بگو فقط میخوای یه روز دیگه هم تحمل کنی و دووم بیاری.حرفایی که باید از گفتنش خودداری کنید  اینا همش تلقین و خیاله همه از یه دوران سخت رد میشن نیمه پر لیوانو ببین چرا میخوای بمیری وقتی این همه چیز برای زندگی کردن داری کاری از دست من برنمیاد فقط از ذهنت بندازش بیرون تا الان باید بهتر شده باشی</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Fri, 10 Feb 2023 00:26:37 +0330</pubDate>
            </item>
                    <item>
                <title>آخرین روزهای یک زندگی کوتاه : افغانستان</title>
                <link>https://virgool.io/@Special_N9NE/last-days-of-a-short-life-qzqoe5haapop</link>
                <description>این پست ترجمه ای است از ویدیو ای به نام : TED talks : a photographic journey through the taliban&amp;#x27;s takeover of Afghanistan که توسط خانوم Kiana Hayeri نقل شده است.سال گذشته، در حالی که به بیستمین سالروز ورود آمریکا به افغانستان رسیده بودیم ، من چندین ماه را صرف عکسبرداری از اصلاحات و تغییرات سریع در افغانستان کردم. در آن زمان ، من میخواستم این ماموریت همانند نامه ای عاشقانه برای سرزمینی باشد که من به مدت هفت سال آن را خانه خطاب میکردم، نمیدانستم که این، تبدیل به نامه خداحافظی با آن افغانستانی میشود که میشناختمش.همراه با همکاران محلی شجاعمان ما به سراسر افغانستان سفر کردیم تا مشاهده کنیم که این 20 سال اخیر چه تاثیری بر مردم گذاشته است. من مخصوصا مشتاق به شنیدن سخن های جوانانی بودم که بعد از ورود آمریکا و در جامعه ای رو به آزادی رشد کرده بودند. آن ها در جهان فیسبوک، توییتر، فیلم ها و شوهای تلویزیونی آمریکایی بزرگ شده بودند. جهانی از آزادی و فرصت های جدید. آن ها همچنین جنگ ، تروریسم، فقر و آوارگی را تحمل کرده بودند و با مشقت زیر سایه حکومت فاسد و تهدید همیشگی بمبگذاری انتحاری زندگی کرده بودند. در حالی که جوانان مناطق روستایی، خود را قربانی شده یا در حال جنگ برای هر دو طرف درگیری متصور می شوند، جوانانی که در شهر ها زندگی می کردند گزینه ها و رویا های بیشتری داشتند. بسیاری از مردان و زنانی که ما با آنها ملاقات کردیم متوسل به آخرین پرتو های امید بودند، در حالی که کشور به دلیل خروج آمریکا و تصاحب قریب الوقوع طالبان در حال غرق شدن در تاریکی بود. برای من مواجهه با این ، شروعی برای یک پایان بود.میخواهم شما را به سفری ببرم، سفری به ماه هایی که منتهی به تصاحب افغانستان بدست طالبان شد و به شما تصاویری از مردمی که در این  زمان گذار زندگی می کنند نشان دهم.در آپریل گذشته، من در کنار دو پسر نوجوان که به دلیل &quot;اتهامات سیاسی&quot; که در حقیقت عبارتی برای کمک به طالبان بود، در سطح سرد سیمانی زندان نشستم. محمد آصف 14 ساله دو برادر داشت که برای دو طرف درگیری مبارزه میکردند، یکی برای طالبان و دیگری برای ارتش. او تصمیم گرفت راه برادرش که در 16 سالگی عضو طالبان شده بود و قبل از اینکه به 20 سالگی برسد کشته شده بود را ادامه دهد. از هر دو پسر پرسیدم که خودشان را در 10 سال آینده تصور کنند. محمد آصف چیزی نگفت و شانه بالا انداخت. من او را تشویق کردم تا بیشتر تلاش کند و خود را بیرون سلول های بی روح زندان ببیند. او چند بار تلاش کرد و ناگهان پاسخ داد &quot;نمیدانم ، احتمالا تا آن موقع مرده ام&quot;در همان سفر، ما به قله کرسای رفتیم، جایی که در آن زمان به عنوان خط مقدم نیرو های ارتش برای جنگ با طالبان شناخته میشد. دامنه کوه مملو از پایگاه هایی بود که سربازان و واحد های شبه نظامی را شامل میشد. بیشتر جنگجویان در اوایل دهه بیست سن خود بودند. بعضی از آنها نوجوان بودند. تقریبا تمامی آن ها ماه ها بود که به خانه نرفته بودند. در آن زمان، واحد تقریبا هر شب در کمین نشسته بودند. مردان جوان تمام شب را بیدار میماندند و میجنگیدند و شیفت عوض میکردند تا بتوانند خواب کوتاهی در طول روز داشته باشند.در دوم جولای ،  یک ماه و نیم قبل از تصاحب افغانستان بدست طالبان، آن ها به قله کرسای حمله کردند. فقط از واحد شبه نظامی 19 مرد کشته شدند و 25 نفر دیگر گروگان گرفته شدند. همه این مردان جوان میراث ورود آمریکا بودند.با حفیظه آشنا شوید. جنگ او را از روستایش بیرون راند و پسرانش را به دشمنان هم تبدیل کرد. جنگ بین خانواده او رخنه انداخت زیرا فرزندانش وارد راه های متفاوتی در زندگی شدند. پسر بزرگش به واحد شبه نظامیان علیه طالبان پیوست و دیگری عضوی از طالبان شد و همچنین دو پسر کوچکترش به استخدام ارتش درآمدند. در اینجا او یک زخم باز بر روی گلویش را نمایان میکند، زخمی که پزشکان معتقد اند ناشی از غم و اندوه است.در طول بازدید من از موسسه ملی موسیقی افغانستان که اولین و تنها آموزشگاه موسیقی در افغانستان است، من با دختری 17 ساله به نام سمبل آشنا شدم که نوازنده ویولون فوق العاده ای بود. او نماد نسلی بود که نسبتا با آزادی بزرگ شده بودند، چیزی که در یک شب از آنها دزدیده شد. خانواده سمبل از یکی از محروم ترین مناطق افغانستان می آمدند. پدر او چندین بار توسط طالبان به دلیل حضور دخترش در مدرسه موسیقی مورد آدم ربایی قرار گرفت. پس از سقوط کابل، طالبان به آن موسسه موسیقی حمله کردند و ساز ها شکسته شدند.  در 26 آگوست، سمبل و عمو/دایی اش وقتی که بمب انتحاری در میان مردم منفجر شد، به همراه هزاران افغان بیچاره و ناامید در بیرون از فرودگاه در حال فرار بودند. سمبل از این تهاجم جان سالم به در برد اما رویا اجرای موسیقی در افغانستان نابود شد، همانطور که 170 تن از کسانی که در آن روز در کنار او بودند نابود شدند.در طول آن تابستان آخر، ما شاهد این بودیم که کشور به سرعت تحت اختیار طالبان قرار میگرفت. وظیفه یکی از همکارانم رصد کردن مناطق سقوط کرده بود، وظیفه دیگری شمارش مردان سقوط کرده. ما با بهت و نا امیدی نظارگر این بودیم که 20 سال پیشرفت در زمینه حقوق زنان، آموزش و آزادی بیان در 20 روز ناپدید شد.در 15 آگوست ، روزی که طالبان کابل را تصرف کرد، من ساعت 4 صبح خانه ام را ترک کردم و به سمت فرودگاه حرکت کردم تا از مردم افغان که با یاس، با وجود پیروزی قریب الوقوع طالبان سعی داشتند از کشور خارج شوند، تصویربرداری کنم. تا ظهر، سربازان طالبان در خیابان ها راهپیمایی میکردند و تا اوایل عصر، آنها ارگ (کاخ ریاست جمهوری افغانستان) را احاطه کردند. این یکی ازسخت ترین تصمیمات زندگی من بود که آن روز کشور را ترک کنم. وقتی که داشتیم با عجله به سمت فرودگاه میرفتیم، صد ها نفر در خیابان ها حراسان به جهت های مختلف میدویدند. هیچوقت ترسی که در چهره مردم بود را فراموش نمی کنم. من احساس گناه زیادی را درونم حس میکردم، گناهم این بود که میروم، گناهم این بود که توانایی رفتن دارم. قلبم شکسته شده بود زیرا میدانستم که احتمالا دیگر نمیتوانم بازگردم، نمیتوانم به خانه ای برگردم که مرا نه فقط به عنوان یک عکاس بلکه به عنوان یک انسان شکل داده بود.بسیاری از افغان ها بعد از سقوط، غرق در غم و ترس شده اند. به تازگی با سمبل و حفیظه در ارتباط بودم تا ببینم اوضاع آنها بعد از سقوط چگونه است. سمبل در حالی که به دنبال رویا هایش در اروپا میدود، سعی میکند تا روح زخم خورده اش را التیام ببخشد. حفیظه به روستا برگشته تا مدتی را با پسرانش بگذراند. زخم او هنوز بهبود نیافته است. من هنوز مجبورم که امیدم را زنده نگه دارم. امید برای کشوری با زخم های باز، که در حال مبارزه برای التیام است.</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Fri, 05 Aug 2022 18:32:30 +0430</pubDate>
            </item>
                    <item>
                <title>مقدمه ای بر تابع های بازگشتی (جاوا)</title>
                <link>https://virgool.io/@Special_N9NE/basic-java-recursion-rdvh9wxekyud</link>
                <description>سلام دوباره. قراره در مورد توابع بازگشتی حرف بزنیم ، یه مثال حل کنیم و ببینیم که با تابع بازگشتی چجوری میشه یه متن رو برعکس کرد و در آخر مزایا و معایبش رو بررسی کنیم :)تابع بازگشتیتابع های بازگشتی یا Recursive functions به تابع هایی میگن که توی خودشون ، خودشونو صدا بزنن.همونطور که میتونید حدس بزنید این باعث میشه که ما یه حلقه داشته باشیم که تا بینهایت ادامه داره و هیچوقت متوقف نمیشه . در این صورت برنامه ما خطای java.lang.StackOverFlowError رو بهمون میده و میگه که این تابع داره تا بینهایت اجرا میشه. پس وظیفه ما اینه که یه نقطه توقفی توی تابع درست کنیم که در اون شرایط تابع وایسته.ا Base Caseهمه توابع بازگشتی برای اینکه تا بینهایت اجرا نشن نیاز به چیزی دارن به اسم base case . این base case شرایطی هستش که تابع دیگه نیازی به صدا زدن خودش نداره و مقدارش از قبل مشخصه. در واقع base case نقطه انتهایی تابع بازگشتی هست که وقتی تابع به این قسمت میرسه متوقف میشه و دیگه خودشو صدا نمیزنه.بیاید یه مثال حل کنیم که این موضوع براتون جا بیوفته :مثالمیخوایم با استفاده از یه تابع بازگشتی ، برنامه ای بنویسیم که یه کلمه بگیره و اونو برعکس کنه.کدهای تابع بازگشتی ما میتونه این شکلی باشه : void reverse(String n) {
    if (n.length() == 0) {
        return;
    } else {
        System.out.print( n.charAt(n.length() - 1) );
        reverse( n.substring(0, n.length() - 1) );
    }
}خب بزارید توضیح بدم اینجا داره چه اتفاقی میوفته. در ورودی تابع یه رشته به اسم n گرفته میشه و بعدش توی شرط چک میشه که طول رشته ما صفر هست یا نه ، اگه صفر نبود else اجرا میشه : توی else اول از همه یه عملیات پرینت داریم که از متدی به اسم charAt استفاده شده . وقتی از این متد روی یه رشته استفاده میکنیم ، میتونیم داخل پرانتز یه index بدیم تا حرفی که اون index رو داره ، برگردونده بشه. مثلا:String name = &quot;ali&quot;;name.charAt(2)   ----&gt; خروجی = iحالا توی مثال ما length -1 رو به عنوان index دادیم که در واقع یعنی آخرین حرف. پس آخرین حرف چاپ میشه.توی خط بعدی خود تابع reverse صدا زده میشه اما بیاید ببینیم به عنوان ورودی بهش چی دادیم :n.substring( 0 , n.length() -1 )متد substring برای این استفاده میشه که قسمتی از رشته رو بگیریم . مقدار اول باید بهش index شروع کلمه مورد نظر رو بدید و در مقدار دوم جایی که کلمه مورد نظرتون تموم میشه مثلا:String text = &amp;quotSalam&quot;
text.substring( 1 , 3 );       // خروجی = alنکته : خود index 3 حساب نمیشه.توی مثال ما index شروع رو دادیم 0 و پایان رو هم دادیم length() -1 تا تابع بازگشتی ما اینبار با مقدار جدید دوباره عملیاتشو انجام بده. این عملیات تا وقتی ادامه داره که شرط if ما درست باشه یعنی n.length == 0 بشه ، اون وقته که تابع دیگه صدا زده نمیشه و ما از تابع reverse خارج میشیم . در واقع در این مثال base case ما میشه وقتی که طول رشته مساوی با صفر هست.برای مثال n = ali این اتفاق میوفته : اول وارد تابع میشیم و شرط length == 0 چک میشه و چون طول ما 3 هست میره روی else . توی اینجا اخرین حرف که i هست چاپ میشه بعدش دوباره تابع reverse صدا زده میشه اما اینبار n رو مساوی al قرار میدیم . (چون با استفاده از substring گفتیم که از اولین حرف تا حرف یکی مونده به اخر رو میخوایم)حالا تابع دوباره شروع به کار میکنه اما با &quot;n = &quot;al . دوباره داخل else میره ، حرف اخر که &quot;l&quot; هست چاپ میشه و تابع یه بار دیگه با &quot;n = &quot;a صدا زده میشه و در اخر بعد اینکه اینکار برای a هم اجرا شد ، دفعه بعدی که تابع صدا زده میشه شرط length == 0 درست میشه و ما دیگه وارد else نمیشیم و تابع تموم میشه.مزایا و معایب تابع بازگشتیتابع های بازگشتی میتونن کد های مارو خیلی کمتر کنن ، وقتی شما مسئله ای رو با روش تابع بازگشتی حل میکنید تعداد خط های کد به مراتب کمتری دارید اما مشکل اینجاس که این توابع خوانایی پایینی دارن و برای آدم سخته که با خوندن کد ها بفهمه که چه اتفاقی داره میوفته پس در نتیجه معمولا رفع اشکال توابع بازگشتی کار خیلی آسونی نیست.همچنین توی تابع بازگشتی RAM بیشتری مصرف میشه و معمولا کد ها کند تر از حالت عادی هستن.</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Fri, 01 Jul 2022 16:50:58 +0430</pubDate>
            </item>
                    <item>
                <title>اندروید Runtime چیه و چیشد که به اینجا رسید؟</title>
                <link>https://virgool.io/@Special_N9NE/android-runtime-iftolcbhk6ab</link>
                <description>توی این پست قراره به اندروید ران تایم بپردازیم. درمورد دالویک و ART و انواع روش هایی که برای اجرای برنامه ها استفاده شده نگاه بندازیم . منبع اصلی اینجاست که این پست از روش ترجمه شده. اگه دوست داشتید میتونید با زبان اصلیش بخونید. اندروید ران تایم (Android runtime) یکی از اجزای اصلی اندرویده. خیلی از شما ممکنه اسم چیزایی مثل ART ، Dalvik ،  JIT یا AOT رو شنیده باشید. اگه شما میخواید بدونید که اینا چی هستن و اندروید ران تایم چجوری کار میکنه جای خوبی اومدید ، توی این پست به این موضوع میپردازیم.اندروید ران تایم چیه؟وقتی ما اپلیکیشنی رو میسازیم و ازش خروجی APK میگیریم، قسمتی از این فایل APK فایل هایی با فرمت dex هستن. این فایل ها شامل کل کد های اپلیکیشن ما به همراه کتابخونه (Library) هایی که استفاده کردیم هستن که البته به صورت Bytecode نوشته شدن.وقتی کاربر اپلیکیشن رو اجرا میکنه ، بایت کد هایی که داخل فایل های dex هستن توسط اندروید ران تایم به زبان ماشین ترجمه میشن تا ماشین این کد ها رو بفهمه و توسط CPU پردازش بشن.اندروید ران تایم بایت کد رو به زبان ماشین تبدیل میکنهبه علاوه کامپایل کردن ، اندروید ران تایم دو تا عمل مهم دیگه یعنی مدیریت حافظه و garbage collection (عملی که باعث میشه پروسه هایی که لازم نیستن بسته بشن) رو انجام میده. چون نمیخوایم این پست طولانی نشه ما به فقط به کامپایل کردن میپردازیم.عمل کامپایل کردن بایت کد به زبان ماشین میتونه با روش های مختلفی انجام بشه که هر کدوم از این روش های مزایا و معایب خودشونو دارن. برای اینکه ما بفهمیم اندروید ران تایم چجوری کار میکنه باید چند سال به عقب برگردیم و اول بفهمیم دالویک (Dalvik) چیه.دالویک (تا اندروید کیت کت 4.4)قبلا در آغاز کار اندروید ، گوشی ها به اندازه الان قوی نبودن. بیشتر گوشی های اون موقع RAM خیلی کمی داشتن حتی کمتر از 200 مگابایت. پس عجیب نیست که اینو بدونید اندروید ران تایم اولیه که اسمش دالویک بود ، مهم ترین و اصلی ترین کارش مدیریت و بهبود استفاده از RAM توسط دستگاه بود.پس بجای اینکه دالویک قبل از اجرای برنامه ها همه رو به زبان ماشین کامپایل کنه، از یه روشی به اسم Just in time (در لحظه) استفاده میکرد که به صورت مخفف بهش میگفتن JIT .در این روش، کامپایلر یه جورایی شبیه یه واسط عمل میکنه و هنگام اجرای هر برنامه ، تیکه های کوچیکی از کد ها رو کامپایل میکنه. پس با این روش چون دالویک فقط کد هایی رو کامپایل میکنه که نیازن و قراره اجرا بشن ، مقدار زیادی RAM صرفه جویی میشه.اما این روش یه عیب داره ، به دلیل اینکه عمل کامپایل کردن موقع اجرای برنامه اتفاق میوفته ، مسلما تاثیر بدی روی عملکرد حین اجرا داره.برای همین یه راه هایی معرفی شدن که باهاشون دالویک بتونه عملکرد بهتری داشته باشه. یکی از راه ها این بود که تیکه کد هایی که زیاد مورد استفاده قرار میگیرن و کامپایل شده هستن ، در حافظه کش (cache) ذخیره میشن تا دیگه دوباره کامپایل نشن. اما یه مشکلی که وجود داشت این بود که RAM در اون زمان خیلی کم بود.این روش چند سالی جواب داد در حالی که عملکرد گوشیا روز به روز بهتر و مقدار RAM اونا روز به روز بیشتر میشد و همینطور که اپلیکیشن ها هر روز بزرگ تر میشدن ، روش JIT هر روز به مشکل بزرگتری تبدیل میشد.و اینجوری شد که توی اندروید Lollipop 5 ، یه اندروید ران تایم جدید معرفی شد که جای دالویک رو بگیره . اسم اون ART بود.ا ART (اندروید Lollipop 5)مدلی که ART کار میکرد 180 درجه با مدلی که دالویک کار میکرد متفاوت بود. ART بجای روش JIT (کامپایل حین اجرا) که توسط دالویک اجرا میشد ، با یه روشی کار میکرد به اسم Ahead of time compilation (کامپایل قبل اجرا) که به اختصار بهش میگن AOT.در ART بجای اینکه کامپایلر عین یه واسط ، حین اجرا کد ها رو کامپایل کنه ، از قبل همه رو کامپایل میکرد پس وقتی یه اپلیکیشن اجرا میشد ، کد ها از قبل آماده شده بودن.این روش تاثیر مثبت بزرگی روی عملکرد حین اجرا داشت و تقریبا 20 برابر سریعتر از JIT بود.مشکلش چی بود ؟ همون طور که انتظار میره ART توی اندروید 5 RAM خیلی بیشتری نسبت به دالویک اشغال میکرد.یه ایراد دیگه این بود که توی اندروید 5 خیلی طول میکشید تا یه اپلیکیشن نصب بشه چون بعد دانلود برنامه ، کل کد ها باید به زبان ماشین کامپایل میشد و همیطور بروزرسانی های سیستمی خیلی بیشتر طول میکشید چون همه برنامه ها باید از دوباره کامپایل و بهینه میشدن.اینجوری شد که توی اندروید nougat 7 روش JIT برگشت اما اینبار با دوست جدیدی به اسم Profile-Guided Compilation.ا Profile-Guided Compilation (کامپایل براساس پروفایل)روش Profile-Guided Compilation  باعث میشه که به طور مداوم عملکرد اپلیکیشن ها وقتی اجرا میشن بهتر بشه. به طور پیشفرض ، اپلیکیشنا با روش JIT کامپایل میشن اما وقتی که ART تشخیص بده که بعضی فعالیت ها &quot;Hot&quot; یا پرکاربرد هستن ، اونا رو از قبل کامپایل میکنه و در حافظه کش ذخیره میکنه تا دستگاه بهترین عملکرد رو داشته باشه.این روش باعث میشه که بهترین عملکرد رو داشته باشیم در حالی که مصرف RAM زیادی هم نداریم. بعدا هم معلوم شد که بیشتر اپلیکیشن ها فقط از 10 تا 20 درصد از کدهاشون پرکاربرد یا همون &quot;Hot&quot; هستن.بعد این تغییری که توی ART رخ داد ، فرایند نصب اپلیکیشن ها دیگه طولانی نبود و همینطور بروزرسانی های سیستمی هم خیلی طول نمیکشید. فرایند کامپایل کردن کد ها از قبل هم فقط وقتی گوشی در حال شارژ یا در حالت Idle ( وقتی که از گوشی استفاده ای نمیشه و بیکاره) هستش اتفاق میوفته تا کمترین تاثیر منفی رو توی گوشی داشته باشیم.تنها مشکلی که وجود داشت این بود که برای اینکه یه profile از یه اپلیکیشنی ساخته بشه ، باید اون اپلیکیشن استفاده میشد. یعنی وقتی شما اپلیکیشنی رو تازه نصب کرده باشید و هنوز ازش استفاده نکرده باشید ، چون هنوز profile ساخته نشده ،ART نمیدونه کجای این اپلیکیشن پرکاربرده ، پس شما با کندی سرعت اپلیکیشن مواجه میشید. مسلما با چند بار استفاده از اپلیکیشن این مشکل از بین میره چون اون موقع Profile ساخته میشه.ایجوری شد که گوگل دست به کار شد و توی اندروید Pie 9 یه چیزی رو معرفی کرد به اسم : Profiles in the cloud به معنی پروفایل ها در  ابر ( خداییش نباید این چیزا رو ترجمه کرد به بزرگی خودتون ببخشید :) ).ا Profiles in the cloud (اندروید 9)طرح و فکر اصلی که توی این روش به کار رفت این بود که بیشتر آدما به یک شکل از اپلیکیشن ها استفاده میکنن. خب پس ما میتونیم دقیقا بعد نصب اپلیکیشن روی گوشی یه کاربر ، اطلاعات پروفایل اون اپلیکیشن رو از کاربرایی که قبلا از اون اپلیکیشن استفاده کردن دریافت کنیم. پس دیگه نیازی به پروفایل سازی نیست . این پروفایل ها دریافت میشن و در فایلی ذخیره میشن که اون فایله شامل یه اطلاعات پروفایل اولیه برای یه اپلیکیشن هست.پروفایل ها همراه با فرایند نصب ، دریافت میشنوقتی که کاربر داره اپ رو نصب میکنه ، در همون موقع پروفایل اپلیکیشن هم دانلود میشه. ART از این اطلاعاتی که دانلود میشن استفاده میکنه تا از قبل بدونه چه کار هایی پرکاربرد هستن تا اونا رو زود تر کامپایل کنه و آماده کنه. با این روش کاربرهایی که اپ جدید نصب میکنن ، در استفاده اولشون از اون اپ ، عملکرد بهتری رو تجربه میکنن.یادتون باشه این چیزا به این معنی نیست که روش قدیمی دیگه منسوخ شده و استفاده نمیشه! بعد اینکه کاربر اپ رو اجرا کرد ، ART اطلاعات کاربر رو جمع آوری میکنه تا ببینه کدوم تیکه های اپ زیاد دارن استفاده میشن ، بعد که اطلاعات کامل جمع آوری شد موقعی که گوشی استفاده ای ازش نمیشه یا توی شارژه اون تیکه های اپ کامپایل و ذخیره میشن.نکته خوشحال کننده اش برای برنامه نویسا اینه که لازم نیس اونا کاری بکنن بلکه Android runtime خودش همه کارا رو انجام میده.خلاصهاندروید ران تایم وظیفه اش اینه بایت کد های اپلیکیشن ها رو به زبون ماشین کامپایل و ترجمه کنه تا CPU کارشو انجام بده. اولین اندروید ران تایم اسمش دالویک بود که از روش کامپایل JIT (کامپایل در لحظه) استفاده میکرد تا استفاده از RAM رو بهبود ببخشه .توی اندروید 5 برای اینکه عملکرد بهتری داشته باشیم ، ART جای دالویک رو گرفت. ART از روش Ahead of time(کامپایل جلو تر از اجرا) یا به اختصار AOT استفاده میکرد. با این روش ما موقع اجرای اپ ها تجربه بهتری داشتیم ولی باید زمان نصب های طولانی و مصرف RAM زیاد رو تحمل میکردیم.بخاطر همین توی اندروید 7 ، روش JIT به اندروید برگشت اما اینبار به همراه روش Profile-guided compilation . توی این روش جدید بخش های پرکاربرد اپ شناسایی میشدن تا از قبل کامپایل بشن. این باعث شد عملکرد دستگاه خیلی بهتر بشه.توی اندروید 9 گوگل برای اینکه کاربرا بهترین عملکرد ممکن رو بعد از نصب اپ تجربه کنن ، Profiles in the cloud معرفی شد تا روش قبلی رو تکمیل کنه. توی این روش پروفایل ها همون اول به همراه فایل APK دانلود میشدن تا از ابتدا ما یه پروفایل آماده برای اپ داشته باشیم.</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Mon, 28 Feb 2022 10:49:20 +0330</pubDate>
            </item>
                    <item>
                <title>شروع کار با Flow در اندروید با کاتلین</title>
                <link>https://virgool.io/@Special_N9NE/kotlin-flow-kzea8pfbqksw</link>
                <description>سلام توی این پست قراره در مورد flow حرف بزنیم که اومده تا کلی از کارا رو آسون کنه :) منبع این پست این زیره اگه خواستید به زبان اصلی بخونیدش: https://proandroiddev.com/kotlin-flow-on-android-quick-guide-76667e872166 همچنین در آخر یه کارایی با LiveData میکنیم، پس اگه باهاش آشنا نیستید میتونید پست زیر رو بخونید: https://virgool.io/@Special_N9NE/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-livedata-%D8%AF%D8%B1-%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-%D8%A8%D8%A7-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-nluzhq8gxg7n چند سالی هست که RxJava وجود داره و نقش مهمی رو توی خیلی از اپلیکیشن های اندرویدی بازی کرده و حالا Kotlin flow رقیب مهمی برای RxJava توی این عرصه هست ولی آیا Flow به اون بلوغی رسیده که جایگزین رقیبش بشه ؟ در این پست قراره چند تا کار مقدماتی انجام بدیم و اطلاعاتی رو از API به رابط کاربریمون منتقل کنیم.این Flow اصلا چیه ؟کاتلین Flow جزئی از Coroutine هست . با Coroutine میتونیم کدهامون رو به صورت نامتقارن(asynchronous) اجرا کنیم. حالا flow میاد و یه قدم بیشتر برمیداره و stream رو اضافه میکنه به داستان.معماریبریم یه نگاه به معماری بندازیم . قراره با معماری MVVM کار کنیم همراه با Repository برای لایه اطلاعاتمون.معمولا در RxJava ما از Observable  در لایه دیتا استفاده میکردیم ، با ViewModel کنترلش میکردیم و با LiveData میفرستادیمش به لایه رابط کاربری یا همون UI . با Flow ما دیگه با Observable کاری نداریم بجاش از تابع های suspendهمراه با Flow استفاده میکنیم. پس معماری ما میشه یه چیزی شبیه تصویر پایین.رتروفیت برای Network Call هارتروفیت (Retrofit) یه کتابخونه خیلی خوبه که میخوایم ازش برای کار کردن با API استفاده کنیم . اگه از Flow استفاده کنید نیازی به کتابخونه دیگه ای نیست چون رتروفیت از ورژن 2.6.0 به بعد از تابع های suspend پشتیبانی میکنه پس دیگه نیازی به return کردن Observable ، Flowable و غیره نیست ، فقط کافیه object مورد نظرتون رو توی یه تابع که suspend هستش return کنید .@GET(&amp;quotfoo&amp;quot)
suspend fun getFoo(): List&lt;Foo&gt;لایه شبکه همینجا تموم میشه.مخزن یا Repositoryخب بیاید در نظر بگیریم که میخوایم اطلاعاتمون رو با استفاده از Network call از repository دریافت کنیم در قالب Flow . خب ما میتونیم کد های مورد نظرمون رو توی بلاک { ... }flow بنویسیم. در این بلاک میتونیم پردازش های مورد نظر رو انجام بدیم مثلا یه درخواست API بدیم بعدش با استفاده از emit  مقدار جدید رو push کنیم.البته شما میتونید از تابع هایی مثل()flowOf یا ()asFlow استفاده کنید که اینا هم میان object شما رو تبدیل به flow میکنن و مقدار رو مستقیم emit میکنن ، در کل از هر روشی که دوست دارید استفاده کنیدخب حالا که flow رو درست کردیم باید کارای map کردن رو انجام بدیم. با عملگر { ... }map.  میشه این کارو انجام داد. اخرشم (...)flowOn. رو اضافه میکنیم تا روی thread درستی باشیم.class FlowRepository(private val api :FlowApiService){
    fun getFoo() : Flow&lt;List&lt;Foo&gt;&gt;{
        return flow{
            // execute API call and map to UI object
            val fooList = api.getFoo()
                .map{ fooFromApi -&gt; FooUI.fromResponse(fooFromApi) }
            emit(fooList)
        }.flowOn(Dispatchers.IO)  // use the IO thread for this Flow    
    }
    fun getFooAlternative(): Flow&lt;List&lt;Foo&gt;&gt;{
        return api.getFoo()
            .map{ fooFromApi -&gt; FooUI.fromResponse(fooFromApi) }
            .asFlow()
            .flowOn(Dispatchers.IO)
    }
}خب ، حالا ما یه repository داریم که یه درخواست API میده و بعدش جواب رو map میکنه و میده به UI object .ا ViewModelتوی viewModel چند تا راه هست برای مدیریت flow ها . اول ما از روش ساده تر استفاده میکنیم که شبیه همون روشی هایی است که توی RxJava هم استفاده میکردیم ، بعدش هم از روش LiveData builder استفاده میکنیم.خب، ما یه repository داریم که یه flow برمیگردونه ولی چجوری این data رو مدیریت کنیم و بفرستیم به رابط کاربری؟ما کارمون رو با object های تغییر پذیر(mutable) و تغییرناپذیر(immutable) شروع میکنیم. کاری که میخوایم بکنیم اینه که مقادیر رو بدیم به متغیر mutable. پس اولین سوال اینه که چجوری اطلاعات رو از repository دریافت کنیم. گرفتن اطلاعات از flow خیلی آسونه ، فقط کافیه از تابع {...}collectاستفاده کنید. اون اطلاعاتی رو که map کردیم و اخرش emit کردیم رو یادتونه ؟ همون اطلاعات رو از طریق collect میشه دریافت کرد.ما فقط وقتی میتونیم از collect استفاده کنیم که یا توی یه coroutine باشیم یا توی یه تابع  suspend . پس ما از viewModelScope  استفاده میکنیم تا بتونیم اطلاعات flow رو دریافت کنیم.private val _foo = MutableLiveData&lt;List&lt;FooUI&gt;&gt;()
val foo: LiveData&lt;List&lt;FooUI&gt;&gt; get() = _foo
fun loadFoo() {
    viewModelScope.launch {
        fooRepository.getFoo()
            .collect { fooItems -&gt;
            _foo.value = fooItems
        }
    }
}خیلی خوب ، ما اطلاعات رو از API گرفتیم و دادیمش به liveData تا توی رابط کاربری اونو observe کنیم. ولی اگه این بین خطایی اتفاق افتاد چی میشه؟ در اینجاست که flow هواتونو داره! ما میتونیم یه مقدار رو emit کنیم وقتی که flow در قسمت {...} شروع به دریافت اطلاعات میکنه و اگر هم خطایی رخ بده توی {...}catch میتونیم مدیریتش کنیم.private val _foo = MutableLiveData&lt;List&lt;FooUI&gt;&gt;()
val foo: LiveData&lt;List&lt;FooUI&gt;&gt; get() = _foo
fun loadFoo() {
    viewModelScope.launch {
        fooRepository.getFoo()
            . { /* _foo.value = loading state */ }
            .catch { exception -&gt; /* _foo.value = error   state */ }
            .collect { fooItems -&gt;
                _foo.value = fooItems
            }
    }
}عالیه ! حالا ما اطلاعات دریافتی رو مدیریت کردیم و مقادیر مختلفی رو به UI فرستادیم.ا LiveData builderدر اینجا به جای اینکه ما دستی مقدار های liveData رو بدیم میتونیم از liveData builder استفاده کنیم. این رو به عنوان یه scope نگاه کنید ، این وقتی که یه UI شروع به observe کردن بکنه ، اجرا میشه و وقتی که هیچ UI نمونده باشه که observe کنه ، قبل تموم شدن کارش متوقف و کنسل میشه. استفاده از liveData builder آسونه ، ما دو تا راه داریم : کد هایی که میخوایم اجرا کنیم رو توی {...}liveData بنویسیم و مقدار رو emit کنیم ، یا اینکه از تابع ()asLiveData استفاده کنیم که دقیقا همون کار رو میکنه.val foo: LiveData&lt;List&lt;FooUI&gt;&gt; = liveData {
    fooRepository.getFoo()
        . { /* emit loading state */ }
        .catch { exception -&gt; /* emit error state */ }
        .collect { fooItems -&gt;
            emit(fooItems)
        }
}
val foo2: LiveData&lt;List&lt;FooUI&gt;&gt; = fooRepository.getFoo()
    .   { /* emit loading state */ }
    .catch { exception -&gt; /* emit error state */ }.asLiveData()و تمام. یه نکته ای که هست اینه که liveData builder قابلیت timeuout هم داره ، یعنی شما میتونید مقداری از زمان رو تعیین کنید که اگه پردازش کد داخل block بیشتر از این مقدار زمان طول کشید ، پردازش متوقف و کنسل بشه.و در همینجا آموزش به اتمام میرسه. ممنون که همراهی کردید اگه دوست داشتید لایک کنید و نظر خودتونو بدید درباره اش و بگید که دوست دارید پست های بعدی در مورد چی باشه :)بدرود.</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Thu, 20 Jan 2022 16:15:59 +0330</pubDate>
            </item>
                    <item>
                <title>شروع لینوکس : دستورات پایه ای :)</title>
                <link>https://virgool.io/@Special_N9NE/linux-basic-commands-ywkuoz1reyfz</link>
                <description>چند تا پنگوئن شاخ :)سلام دوباره، از اخرین و تنها پستم توی اینجا نزدیک 2 سال میگذره ، الانم اومدم گرد و خاک ها رو پاک کنم اما اینبار موضوع دستورات لینوکس هستش D:بدون وقت تلف کردن قراره چند تا دستور پایه ای رو مرور کنیم و با قدم های کوچیکمون توی دنیای بزرگ لینوکس راه بریم :)خب بزن بریم .1. lsاین دستور محتویات دایرکتوری یا فایل رو لیست میکنه . توی برخی از نسخه ها که از کد های رنگی پشتیبانی میکنن ، اسم های با رنگ آبی نشان دهنده اسم دایرکتوری ها هستن.اگه از دستور ls –l استفاده کنین خروجی رو صفحه بندی میکنه و به صورت صفحه به صفحه میتونید ببینید و داخلش حرکت کنید . یادتون باشه همیشه میتونید با ctrl + C دستور رو متوقف کنید و به Command-line برگردید.$ ls -l dirname2. cdاگه میخواین دایرکتوری رو عوض کنید و بین اون ها جا به جا بشید باید از دستور cd استفاده کنید.  اگه میخواید یه مسیر بدید از / استفاده کنید، مثل این :$ cd /dir1/dir23. grepپیدا کردن یه متن داخل یه فایل. دستور grep دنبال متنی که شما میخواید داخل مسیری که دادید میگرده تا پیداش کنه . مثلا برای اینکه کلمه failed رو داخل فایل transaction.log پیدا کنیم ، باید این دستور رو بنویسیم :$ grep ‘failed’ transaction.log4. sudoیه سری دستورات توی لینوکس برای اینکه اجرا بشن به دسترسی های مهمی احتیاج دارن پس شما باید اون دستورات رو به عنوان System administrator اجرا کنید نه یک کاربر عادی. اگه شما میخواید دستوری رو به عنوان System administrator اجرا کنید باید از دستور sudo استفاده کنید و بعد اینکه دستور اجرا شد به حالت کاربر عادی برمیگردید.برای مثال برای اینکه کامپیوتر بعد 2 دقیقه خاموش بشه این دستور رو اجرا میکنیم :$ sudo shutdown 25. pwdبرای اینکه ببینید رو کدوم دایرکتوری هستید و اطلاعاتی در مورد اون دایرکتوری بدست بیارید باید از این دستور استفاده کنید.$ pwd6. passwdبا اینکه انگار این دستور با دستور pwd شبیه هستن ولی کار های متفاوتی رو انجام میدن. این دستور برای عوض کردن رمز کاربر استفاده میشه. شما میتونید رمز خودتون یا رمز کاربرای دیگه رو عوض کنید. حواستون باشه که کاربر های معمولی ممکنه فقط بتونن رمز خودشونو عوض کنن در حالی که کاربر root میتونه رمز همه رو عوض کنه.مثال : عوض کردن رمز کاربر user_one :$ passwd user_one7. mvبرای انتقال یه فایل یا عوض کردن اسمش باید از این دستور استفاده کنیدعوض کردن اسم فایل first به second :$ mv first.txt second.txtانتقال فایل first به آدرس داده شده :$ mv first.txt /Downloads/docs8. cpاین دستور برای کپی کردن هستش . مثالا برای اینکه بخواید از فایل  second توی همون دایرکتوری کپی بگیرید:$ cp second.txt third.txt9. rmبرای حذف کردن فایل های داخل یه دایرکتوری یا خود دایرکتوری از این دستور استفاده میشه . یه نکته ای که هست اینه که یک دایرکتوری اگه خالی نباشه نمیتونید حذفش کنید .مثال : حذف file1 :$ rm file1حذف کل محتویات دایرکتوری myproject و خود دایرکتوری :$ rm -r myproject10. mkdirاین دستور برای ساخت دایرکتوری استفاده میشه :$ mkdir myproject11. chmodبرای تغییر دسترسی ها برای یک شی File system استفاده میشه . فایل ها میتونن دسترسی های r- (خواندن) ، w- (نوشتن) و x- (اجرا) داشته باشن .جدول دستورات برای دادن دسترسی مورد نظر برای مثال ما میخوایم دسترسی ها رو به یک فایل مشخص کنیم ، عدد اول (7) نشان دهنده نحوه دسترسی به فایل برای کاربرانی هست که با فایل مرتبط هستن ، عدد دوم (4) نحوه دسترسی برای گروهی که با فایل در ارتباط هستن و عدد اخر برای هر کسی که جزو اون گروه ها یا کاربر هایی که تعیین شده نیست.$ chmod 744 script.sh12. chownاین دستور برای تغییر مالکیت یک یا چند فایل/دایرکتوری برای یک یا چند کاربر/گروه استفاده میشه .مثال : تغییر مالکیت فایل script.sh به کاربر user1 :$ chown user1 script.sh13. catدستور cat یکی از پرکاربرد ترین دستورات لینوکس هست . با این دستور میتونید یک یا چند فایل ایجاد کنید، محتویات فایل رو ببینید و محتویات فایل ها رو به هم بچسبونید (الحاق کنید).مثال : نشون دادن محتویات فایل file.txt :$ cat file.txt14. echoبرای نشون دادن متن مورد نظر در خروجی استفاده میشه.مثلا میخوایم متن Hello World رو توی خروجی ترمینال چاپ کنیم :$ echo “Hello World”15. wcاین دستور کارش نشون دادن تعداد خط ها ، کلمات ، بایت ها و کارکترها داخل فایل مشخص شده هستش .جدول آپشن های دستورمثال : تعداد خط های داخل فایل readme.txt :$ wc –l readme.txt16. manدستور man یک توضیح یک خطی از دستور مورد نظر شما بهتون میده که میتونه خیلی کمکتون کنه. مثال : نشون دادن توضیح یک خطی برای دستور mkdir :$ man mkdir17. historyاین دستور میتونه بهتون دستورات قبلیتون رو نشون بده یا اینکه اطلاعاتی در مورد دستورات اجرا شده توسط یک کاربر رو بهتون بگه.$ history18. clearاگه این دستور رو اجرا کنید صفحه کامند لاین شما خالی میشه .$ clear19. apt-getاین یک دستور خیلی خوب و قوی برای توزیع های برپایه دبیان / اوبونتو هستش و برای نصب کردن بسته های نرم افزاری ، حذف نرم افزار ها ، آپدیت کردن نرم افزار و همینطور آپدیت کردن کل سیستم عامل استفاده میشه.$ sudo apt-get updateخب بنظر برای شروع کافیه حالا وقتشه که برید با این دستور ها ور برید و تمرین کنید .تا یادم نرفته اینم بگم که این پست از اینجا ترجمه شده ، اگه دوست داشتید میتونید متن اصلی رو بخونید.اگه خوشتون اومد و چیزی یاد گرفتید ممنون میشم لایک کنید و حتما نظرتونو بگید :)بدرود.</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Wed, 17 Nov 2021 22:39:15 +0330</pubDate>
            </item>
                    <item>
                <title>آموزش LiveData در اندروید به زبان ساده با کاتلین</title>
                <link>https://virgool.io/@Special_N9NE/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-livedata-%D8%AF%D8%B1-%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-%D8%A8%D8%A7-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-nluzhq8gxg7n</link>
                <description>سلام رفقا . من تصمیم گرفتنم بخاطر اینکه منابع فارسی برای آموزش LiveData خیلی کمه ، این مقاله رو که بنظرم خیلی خوب و آسونه رو به فارسی ترجمه کنم :) اگه آدرس گیت هاب یا توییتر و .. نویسنده رو میخواید به همونجا مراجعه کنید . پس بزن بریم . این LiveData اصلا چی هست ؟؟live dataخب LiveData در واقع یه Class هست که یه اطلاعات قابل مشاهده ای (که باید بهش بگین Observable) رو نگه میداره . برخلاف Observable های عادی ، LiveData از Lifecycle آگاه هست ، این یعنی به چرخه حیات بقیه کامپوننت ها مثل اکتیویتی ها ، فرگمنت ها یا سرویس ها احترام میزاره . این ویژگی تضمین میکنه که LiveData فقط اون Observer (ناظر) هایی از اپ رو بروزرسانی میکنه که در چرخه حیات حالت فعال دارنبریم یه نگاهی بندازیم و ببینیم چی شد که LiveData به وجود اومد ؟وقتی که اندروید برای اولین بار معرفی شد ، همه اومدن کل کد هاشون رو توی یه اکتیویتی نوشتن .کل کدها درون یک اکتیویتی به هر حال این خوب نیست که کل کد ها رو توی یه اکتیویتی بنویسیم ، همچنین اکتیویتی های اندروید برای Unit Testing هم خیلی سخت هستن .بعد این ماجرا ، همه با مدل های معماری مختلف پا به میدون گذاشتن مثل MVP , MVC , MVVM و بقیه . منظورم از این معماری ها جایی هستن که قسمت منطقی برنامه توسط قسمت های جداگانه ای مثل Presenter , ViewModel , Controller و ... انجام میشن .اکتیویتی کل چرخه حیات رو میدونه و اونو به Presenter یا ViewModel میگه این روش بهتریه بخاطر اینکه قسمت منطقی رو از View جدا میکنه ولی خب این روش هم مشکلات خودشو داره . Presenter , ViewModel , Controller و غیره از چرخه حیات اکتیویتی آگاه نیستن . بخاطر همین نیاز دارن که اکتیویتی چرخه حیات رو بهشون بگه .پس گوگل شروع کرد به فکر کردن که نذاره هر کی هر کاری دوست داره بکنه ، پس Architecture Components رو معرفی کرد .کامپوننت هایی مثل ViewModel یه قابلیت ویژه دارن . اونا میتونن که از چرخه حیات اکتیویتی آگاه باشن و دیگه لازم نیست که اکتیویتی اینو بهشون بگه .منظورم اینه که این برای ارتباط برقرار کردن اطلاعاتیه که از ViewModel به اکتیویتی میرن . این اطلاعات در واقع این قابلیت رو دارن که از چرخه حیات اکتیویتی مطلع باشن . بخاطر همین بهش میگن LiveData ، اطلاعاتی که از چرخه حیات ناظر هاشون ( مثلا اکتیویتی ) آگاه هستن .شکل کامل تر ...برای ترسیم بهتر ، LiveData رو وسط نقشه میزاریم .ترسیم مکان و نقش LiveData توی این نقشه شما میتونین ببینید که LiveData میتونه توسط ViewModel ها تغییر پیدا کنه ( یا هر جایی که برای تغییر LiveData استفاده میکنین )این نکته رو در نظر بگیرید که بعد از تغییر ، LiveData دوست داره که Observer هاش ( مثلا اکتیویتی ، فرگمنت ، سرویس و ... ) رو مطلع بکنه . به هر حال LiveData چشم بسته همه رو مطلع نمیکنه ولی بجاش اول وضعیت اونا رو بررسی میکنه .اگه Observer فعال باشه بعدش میتونه اطلاعاتی که داخل LiveData تغییر داده شدن رو اطلاع بده . به هر حال اگه Observer درحالت Paused یا Destroyed باشه ، اون وقت مطلع نمیکنه .این خیلی خوبه چون لازم نیس ما درباره  و onDestroy نگران باشیم :)علاوه بر این وقتی Observer میره توی حالت Resume ، یهویی از اخرین اطلاعات LiveData مطلع میشه .انواع LiveDataدرواقع LiveData یک abstract class هستش بخاطر همین نمیشه از خودش استفاده کرد ولی خوشبختانه گوگل چند تا کلاس implement کرده که میتونیم از اونا استفاده کنیم . ا MutableLiveDataاین یکی از ساده ترین LiveData ها هستش که فقط اطلاعات اپدیت شده رو دریافت میکنه و به Observer هایی که داره اطلاع میده . به همین راحتی میتونین MutableLiveData رو تعریف کرد //c -  تعریف کردن
val liveDataA = MutableLiveData&lt;String&gt;()

// تغییر دادن مقدار
liveDataA.value = someValue

//  میتونید از این هم استفاده کنید liveDataA.postValue(value) 
// انجام بشه UI tread  تا دربرای Observe کردن اطلاعات هم کار ساده ای رو باید انجام بدیم . من یه فرگمنت دارم که liveDataA رو به صورت زیر Observe میکنه class MutableLiveDataFragment : Fragment() {

    privateval changeObserver = Observer&lt;String&gt; { value -&gt;
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        getLiveDataA().observe(this, changeObserver)
    }  
  // بقیه ی کد های فرگمنت ...
}نتیجه به شکل زیره . وقتی که اطلاعات مثلا 7567 و 6269 در liveDataA ذخیره میشن ، توسط فرگمنت تشخیص داده میشن داخل کد ، شما میتونید این قسمت رو ببینید getLiveDataA().observe(this, changeObserver)ولی هیچ کدی وجود نداره که وقتی فرگمنت pause یا terminate میشه ، LiveData اونو بیخیال بشه . حتی بدون انجام اینکار ، هیچ مشکلی برای ما پیش نمیاد :)گیف زیر رو نگاه کنید ، حتی وقتی که فرگمنت حذف میشه ، مقدار liveDataA یعنی 1428 هیچ خطایی بخاطر قرار دادن یک مقدار توی یه فرگمنت غیرفعال نمیده . این نکته رو هم در نظر داشته باشید که وقتی فرگمنت فعال میشه ، اخرین اطلاعات رو از liveDataA دریافت میکنه . ا Transformations.Mapفرض کنید که شما یه اطلاعاتی رو از یک Repository دریافت میکنید و قبل از اینکه اطلاعات رو به View بفرستید ، میخواید که اونو تغییر بدید . ما هنوزم میتونیم از LiveData برای انتقال اطلاعات بین موجودیت های مختلف استفاده کنیم . انتقال اطلاعات از repository به viewModel و بعدش به view توسط LiveDataما میتونیم اطلاعات رو از یک LiveData به یک LiveData ی دیگه بفرستیم ، اونم با استفاده از فانکشن ()transformations.mapclass TransformationMapFragment : Fragment() {

    privateval changeObserver = Observer&lt;String&gt; { value -&gt;
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        val transformedLiveData = Transformations.map(
                getLiveDataA()) { &amp;quotA:$it&amp;quot }
        transformedLiveData.observe(this, changeObserver)
    }    
// بقیه ی کد های فرگمنت ...
}و نتیجه میشه گیف زیر . اطلاعات که در اینجا 5116 هستش تبدیل میشه به A:5116 و در فرگمنت نمایش داده میشه . یکی از مزایای استفاده از ()transformations.map اینه که مطمئن میشیم که وقتی مقصد ( مثلا ViewModel و View ) غیرفعال هستش ، LiveData اطلاعات رو منتقل نمیکنه . بهتره یه نگاهی به کد های خود ()transformations.map بندازیم و ببینیم که داره چیکار میکنه ...@MainThread
public static &lt;X, Y&gt; LiveData&lt;Y&gt; map(@NonNull LiveData&lt;X&gt; source,
        @NonNull final Function&lt;X, Y&gt; func) {
    final MediatorLiveData&lt;Y&gt; result = new MediatorLiveData&lt;&gt;();
    result.addSource(source, new Observer&lt;X&gt;() {
        @Override
        public void d(@Nullable X x) {
            result.setValue(func.apply(x));
        }
    });
    return result;
}عه ! توی این کد ها میبینیم که از یه نوع LiveData دیگه استفاده شده که اسمش MediatorLiveData هست . بریم ببینیم این چطوریه ...ا MediatorLiveDataاگه کد های بالا رو بررسی کنید ، میفهمید که جذاب ترین قسمت MediatorLiveData ، قابلیت اضافه کردن منبع اطلاعات به اون و کدی که محتوای اطلاعات رو تغییر میده هستش .این به این معنی هستش که ما میتونیم چند LiveData به یک مقصد از طریق MediatorLiveData رو داشته باشیم  . دو LiveData که از طریق MediatorLiveData به یک مقصد میرنما میتونیم به صورت مستقیم از MediatorLiveData به شکل زیر استفاده کنیم . class MediatorLiveDataFragment : Fragment() {

    privateval changeObserver = Observer&lt;String&gt; { value -&gt;
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        val mediatorLiveData = MediatorLiveData&lt;String&gt;()
        mediatorLiveData.addSource(getliveDataA())
              { mediatorLiveData.value = &amp;quotA:$it&amp;quot }
        mediatorLiveData.addSource(getliveDataB()) 
              { mediatorLiveData.value = &amp;quotB:$it&amp;quot }
        mediatorLiveData.observe(this, changeObserver)
    }    
// بقیه ی کد های فرگمنت ...
}با استفاده از کدهای بالا ، ما نتیجه زیر رو داریم . فرگمنت میتونه اطلاعات تغییر داده شده ی liveDataA و liveDataB دریافت کنه . اینجا یه نکته وجود داره . وقتی فرگمنت فعال نیس و اطلاعات liveDataA و liveDataB تغییر پیدا میکنه ، وقتی فرگمنت فعال میشه ، MediatorLiveData اطلاعات اون LiveData ای رو میگیره که اخرین بار اون اضافه شده باشه ، که توی گیف زیر liveDataB هستش .همونطور که دیدید ، فرگمنتی که دوباره فعال شده همش داره اطلاعات liveDataB رو میگیره ، بی توجه به اینکه کدوم LiveData اخرین بار اطلاعاتش تغییر کرده . این بخاطر اینه که توی کد میتونید ببینید که liveDataB اخرین source ای هستش که به MediatorLiveData اضافه شده . ا Transformations.SwitchMapاینکه ما این قابلیت رو داشته باشیم که اطلاعات دو تا منبع رو داشته باشیم خیلی خوبه ولی اگه بخوایم فقط یکی رو کنترل کنیم و اگه نیاز بود بین اون ها switch کنیم باید چیکار کنیم ؟ آیا باید یه کد منطقی براش بنویسیم ؟ :( ما میتونیم این کارو انجام بدیم بدون اینکه نیاز به منطق باشه D: گوگل یه فانکشن خیلی باحال دیگه ارائه کرده به اسم ()Transformations.switchMap . @MainThread
public static &lt;X, Y&gt; LiveData&lt;Y&gt; switchMap(@NonNull LiveData&lt;X&gt; trigger,
        @NonNull final Function&lt;X, LiveData&lt;Y&gt;&gt; func) {
    final MediatorLiveData&lt;Y&gt; result = new MediatorLiveData&lt;&gt;();
    result.addSource(trigger, new Observer&lt;X&gt;() {
        LiveData&lt;Y&gt; mSource;

        @Override
        public void d(@Nullable X x) {
            LiveData&lt;Y&gt; newLiveData = func.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            if (mSource != null) {
                result.removeSource(mSource);
            }
            mSource = newLiveData;
            if (mSource != null) {
                result.addSource(mSource, new Observer&lt;Y&gt;() {
                    @Override
                    public void d(@Nullable Y y) {
                        result.setValue(y);
                    }
                });
            }
        }
    });
    return result;
}این فانکشن یه source رو اضافه میکنه و source قبلی رو حذف میکنه . پس ما همیشه یه دونه source برای MediatorLiveData داریم . پس دیاگرام ما این شکلی میشه :و کدش هم این مدلی میشه : class TransformationSwitchMapFragment : Fragment() {

    privateval changeObserver = Observer&lt;String&gt; { value -&gt;
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)

        val transformSwitchedLiveData =
            Transformations.switchMap(getLiveDataSwitch()) { 
                switchToB -&gt;
                if (switchToB) {
                     getLiveDataB()
                } else {
                     getLiveDataA()
                }
        }

        transformSwitchedLiveData.observe(this, changeObserver)
    }   
// بقیه ی کد های فرگمنت ...
}با استفاده از این ما به راحتی میتونیم کنترل کنیم که کدوم اطلاعات در View نمایش داده بشه . این خیلی برای برنامه هایی بدرد میخوره که اطلاعاتی رو از منابع مختلف دریافت میکنن و توسط یه تنظیم معینی کنترل میشن ( مثل قسمت login کردن کاربر ) . منابع نویسنده شما میتونین سورس کد ها رو از این گیت هاب ببینید.  https://github.com/elye/demo_android_livedata_illustration اجراش کنید و باهاش بازی کنید تا بهتر بفهمیدش و درک کنید :) دمتون گرم که اینو خوندید و امیداورم یادگرفته باشید . اگه خوشتون اومد لایک یادتون نره و همچنین میتونید با استفاده از این لینک برام یه قهوه بخرید :) ❤️ بدرود .</description>
                <category>Special N9NE</category>
                <author>Special N9NE</author>
                <pubDate>Sun, 01 Mar 2020 01:02:35 +0330</pubDate>
            </item>
            </channel>
</rss>