<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های سعید هنری</title>
        <link>https://virgool.io/feed/@saeedhonari.developer</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-07 11:55:17</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1715669/avatar/dqP110.jpeg?height=120&amp;width=120</url>
            <title>سعید هنری</title>
            <link>https://virgool.io/@saeedhonari.developer</link>
        </image>

                    <item>
                <title>بازتاب در کاتلین (reflection in kotlin)</title>
                <link>https://virgool.io/@saeedhonari.developer/%D8%A8%D8%A7%D8%B2%D8%AA%D8%A7%D8%A8-%D8%AF%D8%B1-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-reflection-in-kotlin-a2turpzcpnzw</link>
                <description>بازتاب(reflection) مانند سایر زبان های برنامه نویسی، این ویژگی قدرتمند برای ما این امکان رو ایجاد میکنه که ساختار کلاس ها، توابع، ویژگی ها(properties) و سایر عناصر کدمون رو در زمان اجرا بررسی کنیم. در حالی که استفاده از این ویژگی میتونه تاثیر مثبت یا منفی روی عملکرد کلی پروژه داشته باشه باید به دقت ازش استفاده کرد و موارد زیادی هست که میشه از reflection به صورت مفید استفاده کرد .در این پست، با اصول reflection درکاتلین آشنا میشیم و چند نمونه ساده رو بررسی می کنیم.درک reflection در کاتلینقابلیت reflection در Kotlin امکان بازرسی و ارتباط با  کد در زمان اجرا رو به ما میده. یعنی  ما می‌تونیم ساختار اشیا، کلاس‌ها و توابع را تجزیه و تحلیل کنیم، توابع را فراخوانی کنیم و به خصوصیات به صورت پویا دسترسی داشته باشیم بدون اینکه در زمان کامپایل از اون‌ها اطلاع داشته باشیم. با اپراتور :: امکان استفاده از این ویژگی رو داریم.مرجع کلاس (class reference)ابتدایی ترین حالت ایجاد یه reference از کلاسه، داخل کد زیر یه نمونه ساده از کلاس کاتلین رو داریم :class Dog(var name: String) {
    fun bark() {
        println(&amp;quotBark!&amp;quot)
    }

    fun bark(sound: String) {
        println(sound)
    }

    private fun hello() {
        println(&amp;quotHello! My name is $name&amp;quot)
    }
}حالا میتونیم ازش یه refrence ایجاد کنیم :val classRef = Dog::classهمچنین، این امکان وجود داره که refrence کلاس رو از یک نمونه که قبلا ایجاد کردیم ، بگیریم:(با همون دستور class::)val myDog = Dog(&quot;Puppy&quot;)val classRef = myDog::classهنگامی که مرجع کلاس رو ایجاد کردیم، میتونیم به ویژگی های refrence دسترسی داشته باشید تا در مورد اون کلاس اطلاعات بیشتری دریافت کنیم. به عنوان مثال، میشه نام کلاس رو پیدا کرد یا بررسی کرد که آیا کلاس ما یه دیتاکلاس  هست یا نه :println(classRef.simpleName) // Dog
println(classRef.qualifiedName) // org.metapx.Dog
println(classRef.isData) // falseدر Kotlin، مرجع کلاس به عنوان نوع Kclass شناخته میشه که مخفف Kotlin class هستش.جدا از بررسی کلاس، Kclass توانایی های جالبی داره به عنوان مثال، متد ()createInstance به ما اجازه می ده یه شی جدید از مرجع کلاس ایجاد کنید:val secondDog = classRef.createInstance()دسترسی به متدهای مرجع کلاس کاتلینما همچنین می‌تونیم به متدهای مرجع کلاس بدون در نظر گرفتن سطح دسترسی بهشون دسترسی داشته باشیم (private, protected, internal,public) . یعنی حتی میشه به توابعprivateیه کلاس نیز از مرجع اون دسترسی داشت:val myDog = Dog(&amp;quotPuppy&amp;quot)
val classRef = myDog::class
classRef.memberFunctions.forEach { 
    println(it.name) 
}نکته : ویژگی MemberFunctions مربوط به Kclass  میشه که تمام متدهای کلاس رو به عنوان یک مجموعه ذخیره می کنه.خروجی کد بالا به شکل زیر چاپ میشه:bark
bark
hello
equals
hashCode
toStringدر ادامه می توانیم تابع رو از مرجع کلاس به صورت زیر صدا بزنیم:val myDog = Dog(&amp;quotPuppy&amp;quot)
val classRef = myDog::classابتدا باید از تابع find برای پیدا کردن تابع مورد نظرمون از کلاس مرجع استفاده کنیم :val barkRef = classRef.memberFunctions.find { 
    it.name == &amp;quotbark&amp;quot 
}
barkRef?.call(myDog)تابع bark اگه موجود باشه (که هست)رو داخل متغیر barkRef ذخیره میکنیم اگه هم متدی رو سرچ کنیم و نباشه براش null ست میشه و بررسی میکنیم اگر null نبود با استفاده از متد call() از نوع مرجع تابع Kfunction استفاده میکنیم تا متدرو صدا بزنیم.اولین آرگومان متد call() باید نمونه ای از مرجع کلاس باشه، به همین دلیله که شی myDog به متد ارسال می شه. وقتی متد ما شما private باشه، باید قبل از فراخوانی متد، ویژگی isAccessible مرجع تابع را به عنوان true تغییر دهید:val helloRef = classRef.memberFunctions.find { 
    it.name == &amp;quothello&amp;quot 
}
helloRef?.isAccessible = true
helloRef?.call(myDog)دسترسی به خصوصیات (properties) مرجع کلاس کاتلیندسترسی به property مرجع کلاس Kotlin  مشابه روشیه که ما به متدهای اون دسترسی داریم . propertyهای یک کلاس در MemberProperties به عنوان یک مجموعه ذخیره می شن. به عنوان مثال، می تونیم مقدار property نام نمونه myDog را به صورت زیر دریافت کنیم:val myDog = Dog(&amp;quotPuppy&amp;quot)
val classRef = myDog::class
val nameRef = classRef.memberProperties.find {
    it.name == &amp;quotname&amp;quot 
}
println(nameRef?.getter?.call(myDog)) // Puppyمرجع property نمونه ای از نوع KProperty هستش که مقدار property با فراخوانی متد getter() بازیابی میشه. برای تغییر مقدار name، باید ابتدا این property را مانند شکل زیر به KMutableProperty کست (cast) کنیم:val myDog = Dog(&amp;quotPuppy&amp;quot)
val classRef = myDog::class
val nameRef = classRef.memberProperties.find {
    it.name == &amp;quotname&amp;quot 
} as KMutableProperty&lt;*&gt;?
nameRef?.setter?.call(myDog, &amp;quotJacob&amp;quot)
println(myDog.name) // Jacobکلاس KMutableProperty متد ()setter را نگه می‌داره که برای تنظیم مقدار property باید اون را صدا بزنیم. تا اینجا یاد گرفته‌ایم که چطور از یک مرجع کلاس به متدها و propertyها دسترسی داشته باشیم. نتیجه گیری امکان Reflection یک ویژگی قدرتمنده که فقط برای نیازهای خاص استفاده میشه. به دلیل توانایی های که ذکر شد، بیشتر برای توسعه یه framework یا library برای توسعه بیشتر استفاده میشه که میشه به framework های JUnit و Spring اشاره کرد که از reflection در کدهاشون استفاده کردن.Reflection به فریم ورک اجازه می‌ده تا با کلاس‌ها و توابع بدون اینکه از قبل در مورد اونها اطلاعاتی داشته باشه، برخورد کنه.سعی کردم reflection در کاتلین رو تا جای ممکن روان توضیح بدم ، انشاالله که نتیجه مثبتی داشته باشه .</description>
                <category>سعید هنری</category>
                <author>سعید هنری</author>
                <pubDate>Tue, 25 Jul 2023 11:08:37 +0330</pubDate>
            </item>
                    <item>
                <title>آرایه SparseArray در اندروید</title>
                <link>https://virgool.io/Rocket/%D8%A2%D8%B1%D8%A7%DB%8C%D9%87-sparsearray-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-mejgfl9kazyp</link>
                <description>سلام دوستان عزیز ، تو این مقاله قصد دارم در مورد مجموعه SparseArray صحبت کنم ولی به نظرم برای شروع بهتره چند تا مفهموم رو با هم مرور کنیم :داده های اولیه ( Primitive Data Types )انواع داده های اولیه هشت اصل تعریف شده در جاوا عبارتند از: int، byte، short، long، float، double، boolean و char. اینها اشیا در نظر گرفته نمی شن و مقادیر خام را نشون میدن. و مستقیما داخل stack ذخیره میشن.کلاس های Wrapper کلاس های Wrapper اصطلاحا به کلاس هایی میگن که توانایی تبدیل مقادیر Primitive Data Types را به Object و برعکس را دارا هستن و امکان استفاده از Primitive Data Type ها رو به عنوان Object به ما میدن.عملیات AutoBoxing  به عملیاتی که طی اون مقدار Primitive Data Type به Object از Wrapper Class تبدیل بشه رو AutoBoxing میگن .عملیات UnBoxingبرعکس عملیات AutoBoxing  هستش و به عملیاتی گفته میشه که طی اون یک Object از Wrapper Class به مقدار Primitive Data Type تبدیل می شه.خوب بریم سراغ اصل مطلب !آرایه SparseArray یه لیست پراکنده اس که میتونه خونه های خالی داشته باشه و با اعداد صحیح  به عنوان کلید پر میشه و جایگزینی برای map هستش ،map نیاز داره که کلیدهای از نوع Object داشته باشه ، زمانی که از داده اولیه int به عنوان کلید در map استفاده کنیم  کامپایلر به طور خودکار این داده اولیه int رو به wrapper class مربوط به خودش تبدیل میکنه (integer) . خوب نکته همین جاست !تفاوت در استفاده از حافظه در map و SparseArray قابل توجه ، به این دلیل که int فقط ۴ بایت از حافظه رو استفاده میکنه ولی integer به ۱۶ بایت از حافظه نیاز داره و SparseArray از int به عنوان مقدار کلید استفاده میکنه . یه نکته ، در SparseArray لازم نیست کلیدها رو مرتب و پشت سرهم وارد کنیم بلکه به صورت خودکار کلیدها به صورت صعودی مرتب میشن نه به ترتیبی که اضافه شدن.عملیات در SparseArray به صورت زیر هستش :اضافه کردن ---------------- sparseArray.put(10,&quot;test1&quot;)پاک کردن ---------------- sparseArray.remove(10)فراخوانی ---------------- sparseArray.get(10)فراخوانی کلید با ایندکس ---------------- sparseArray.keyAt(0)فراخوانی مقدار با ایندکس ---------------- sparseArray.valueAt(0)خیلی خوشحال میشم در صورتی که این نوشته رو دوست داشتید ❤️ کنید یا نظرتون را از طریق کامنت برام بنویسید.</description>
                <category>سعید هنری</category>
                <author>سعید هنری</author>
                <pubDate>Mon, 18 Jul 2022 11:45:32 +0430</pubDate>
            </item>
                    <item>
                <title>پیاده سازی ذخیره و بازیابی وضعیت در CustomView (اندروید استودیو و کاتلین)</title>
                <link>https://virgool.io/@saeedhonari.developer/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%B0%D8%AE%DB%8C%D8%B1%D9%87-%D9%88-%D8%A8%D8%A7%D8%B2%DB%8C%D8%A7%D8%A8%DB%8C-%D9%88%D8%B6%D8%B9%DB%8C%D8%AA-view-%D8%AF%D8%B1-customview-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%D8%A7%D8%B3%D8%AA%D9%88%D8%AF%DB%8C%D9%88-%D9%88-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-vtenzmyyntmo</link>
                <description>داخل پروژه ای که الان دارم روش کار میکنم به یه مشکل مربوط به ذخیره و بازیابی وضعیت View در CustomView های که  خودمون ایجاد کرده بودیم برخورد کردم. من راه حل های مختلف را داخل یه پروژه جداگانه آزمایش کردم تا به این راه حل رسیدم.در ابتدا بیاید سعی کنیم به این سوال پاسخ بدیم. چرا باید وضعیت View رو ذخیره کنیم؟موقعیتی رو تصور کنید که یه پرسشنامه طولانی رو داخل یه اپلیکیشن دارید پر میکنید. تو یه لحظه، شما به طور تصادفی صفحه گوشی رو می چرخونید یا لازم دارید موردی رو داخل برنامه دیگه ای بررسی کنید  و پس از بازگشت به پرسشنامه، معلوم میشه که همه قسمت ها خالی شدن!!! چنین وضعیت بدی به طور قابل توجه ای کاربرو از استفاده از برنامه منصرف می کنه. برای مثال، با من همراه باشید تا یه CustomView  ایجاد کنیمکد مربوط به فایل xml :&lt;?xml version=&amp;quot1.0&amp;quot encoding=&amp;quotutf-8&amp;quot?&gt;
&lt;LinearLayout xmlns:android=&amp;quothttp://schemas.android.com/apk/res/android&amp;quot
    android:layout_width=&amp;quotmatch_parent&amp;quot
    android:layout_height=&amp;quotmatch_parent&amp;quot
    android:orientation=&amp;quothorizontal&amp;quot&gt;

    &lt;androidx.appcompat.widget.SwitchCompat
        android:layout_width=&amp;quotwrap_content&amp;quot
        android:layout_height=&amp;quotwrap_content&amp;quot /&gt;

    &lt;EditText
        android:layout_width=&amp;quot0dp&amp;quot
        android:layout_height=&amp;quotwrap_content&amp;quot
        android:layout_weight=&amp;quot1&amp;quot /&gt;
&lt;/LinearLayout&gt;کد مربوط به کلاس :class CustomSwitchViewNoId: FrameLayout {
    // constructors
    init {
    LayoutInflater.from(context).inflate(R.layout.view_custom_switch_no_id, this)
    }
}به جای اینکه هر بار صفحه رو بچرخونیم تا نتیجه رو ببینیم، می‌تونیم گزینه زیر رو تو تنظیمات  Developer options گوشی فعال کنیم(Settings -&gt; Developer options -&gt; Don’t keep activities -&gt; این گزینه رو روشن کنیم)حالا وقشه پروژه رو اجاره میکنیم و دکمه home گوشی رو بزنیم تا عملیات ریستو داشته باشیم.همانطور که می بینید با ریست شدن اکتیویتی یا فرگمنت وضعیت View ذخیره نمیشه و ما متن و حالت انتخاب شده SwitchCompat رو از دست میدیم.خوب مشکل اصلی همین جاست !اجازه بدید سلسله مراتبی رو که توسط اندروید برای ذخیره و بازیابی وضعیتView فراخوانی میشه رو بررسی کنیم:Save statesaveHierarchyState(SparseArray&lt;Parcelable&gt; container)dispatchSaveInstanceState(SparseArray&lt;Parcelable&gt; container)onSaveInstanceState()Restore staterestoreHierarchyState(SparseArray&lt;Parcelable&gt; container)dispatchRestoreInstanceState(SparseArray&lt;Parcelable&gt; container)onRestoreInstanceState(Parcelable state)پیاده سازی State Savingحالا میرسیم به اصل موضوع. در قدم اول فرآیند ذخیره و بازیابی ویژگی ها در یک View :برای انجام این کار، باید دو متد onSaveInstanceState و onRestoreInstanceState را Override کنیم. onSaveInstanceState یک parcelable را برمی‌گردونه که حالت ذخیره شده view ماست و به onRestoreInstanceState یک parcelable ارسال میشه که می تونیم وضعیت ذخیره شده view رو از اون بازیابی کنیم.در onSaveInstanceState، باید super.onSaveInstanceState را فراخوانی کنیم تا به کلاس super اجازه بدیم ذخیره وضعیت رو داشته باشه و parcelable ای که نشون دهنده وضعیت view هستش رو برگردونه. ما نمی‌تونیم مقداری به parcelable برای کلاس super اضافه کنیم، بنابراین به راهی برای کپسوله‌سازی وضعیت ذخیره‌شده کلاس super، و همچنین ذخیره وضعیت CustomView نیاز داریم. اندروید کلاس View.BaseSavedState  رو  ارائه می ده که می تونیم برای این منظور گسترشش بدیم.میشه بیشتر هم توضیح داد ولی به نظرم تا همین جا هم کافی باشه !!!خوب بریم تو کد :    class SavedState(superState: Parcelable?) : BaseSavedState(superState) {        val childrenStates by lazy {            SparseArray&lt;Parcelable&gt;()      }        override fun writeToParcel(out: Parcel, flags: Int) {            super.writeToParcel(out, flags)             out.writeSparseArray(childrenStates)        }    }در مورد SparseArray داخل یه پست دیگه توضیح میدم فعلا فقط اینو بدونید که جایگزینی برای Map هستش و ما وضعیت View ها رو داخل متد writeToParcel ای که override کردیم داخل آرایه ای از جنس SparceArray ذخیره میکنیم.    public override fun onSaveInstanceState(): Parcelable? {        val saveState = SavedState(super.onSaveInstanceState())        for (i in 0 until childCount) {            getChildAt(i).saveHierarchyState(saveState.childrenStates)        }        return saveState    }    public override fun onRestoreInstanceState(state: Parcelable) {        when (state) {            is SavedState -&gt; {                super.onRestoreInstanceState(state.superState)                dispatchSaveInstanceState(state.childrenStates)            }            else -&gt; super.onRestoreInstanceState(state)        }    }در مورد متدهای onSaveInstanceState و onRestoreInstanceState و نحوه ی عملکردشون چند خط بالاتر توضیح دادم.نکته : با وجود تمام کدهایی که ایجاد کردیم هنوز هم عملیات ذخیره و بازیابی وضعیت View انجام نمیشه !!!اگر برای View آی دی ست نکرده باشیم، اندروید برای ذخیره وضعیت view هیچ تلاشی نمیکنه پس حتما به این نکته باید توجه داشت که برای هر view ای در CustomView برای ذخیره وضعیت نیاز به تعریف ‌id داریم.&lt;?xml version=&amp;quot1.0&amp;quot encoding=&amp;quotutf-8&amp;quot?&gt;
&lt;LinearLayout xmlns:android=&amp;quothttp://schemas.android.com/apk/res/android&amp;quot
    android:layout_width=&amp;quotmatch_parent&amp;quot
    android:layout_height=&amp;quotmatch_parent&amp;quot
    android:orientation=&amp;quothorizontal&amp;quot&gt;

    &lt;androidx.appcompat.widget.SwitchCompat
        android:id=&amp;quot@+id/customViewSwitchCompat&amp;quot
        android:layout_width=&amp;quotwrap_content&amp;quot
        android:layout_height=&amp;quotwrap_content&amp;quot /&gt;

    &lt;EditText
        android:id=&amp;quot@+id/customViewEditText&amp;quot
        android:layout_width=&amp;quot0dp&amp;quot
        android:layout_height=&amp;quotwrap_content&amp;quot
        android:layout_weight=&amp;quot1&amp;quot /&gt;
&lt;/LinearLayout&gt;خروجی کار به صورت زیر میشه :تا به این جای کار میتونیم وضعیت view رو حفظ و بازیابی کنیم اما بازهم یه مشکل وجود داره!   https://virgool.io/d/vtenzmyyntmo/%D8%A7%DB%8C%D9%86%D9%85%D8%B4%DA%A9%D9%84%D9%88%D9%82%D8%AA%DB%8C%D8%AE%D9%88%D8%AF%D8%B4%D9%88%D9%86%D8%B4%D9%88%D9%86%D9%85%DB%8C%D8%AF%D9%87%DA%A9%D9%87%DA%86%D9%86%D8%AF%D8%AA%D8%A7View%D8%A7%D8%B2CustomView%D8%A8%D9%87Layout%D8%A7%D8%B6%D8%A7%D9%81%D9%87%DA%A9%D9%86%DB%8C%D9%85%D8%8C همانطور که می بینید، تگ های id/customViewSwitch+@ و id/customViewEditText+@ به تعداد View های ما تکرار می شن، بنابراین SparseArray  تولید شده فرزندان id/switch1+@، سپسid/switch2+@ و در نهایت id/switch3+@ را ذخیره می کنه.رفتار SparseArray برای ذخیره دیتا به صورت زیر هستش (حتما پست مربوط به SparseArray رو بخونید)val array = SparseArray()
array.put(1, &amp;quottest1&amp;quot)
array.put(1, &amp;quottest2&amp;quot)
array.put(1, &amp;quottest3&amp;quot)
Log.i(&amp;quotTAG&amp;quot, array.toString())نتیجه به صورت زیر هستش :‌I/TAG: {1=test3}بنابراین مقادیر جدید مقادیر قبلی رو  override می کنن. ،تصویر زیر کامل میتونه نحوه ی ذخیره سازی view هارو نشون بده که چرا هر یک از viewها حالت آخرین فرزندو دریافت میکنن.با این حال، چگونه می تونیم جلوی این اتفاقو بگیریم؟ راه حل اینجاست !در ابتدا، ما باید دو callback ( dispatchSaveInstanceState و dispatchRestoreInstanceState)  رو override کنیم:override fun dispatchSaveInstanceState(container: SparseArray&lt;Parcelable&gt;){
    dispatchFreezeSelfOnly(container)
}override fun dispatchRestoreInstanceState(container: SparseArray&lt;Parcelable&gt;) {
    dispatchThawSelfOnly(container)
}به این ترتیب()super.onSaveInstanceState تنها حالت super رو برمی‌گردونه و از مشاهده فرزندان اجتناب می‌کنه. ما اکنون آماده هستیم تا وضعیت را در داخل onSaveInstanceState و onRestoreInstanceState مدیریت کنیم. حالا وقتشه با فرآیند ذخیره وضعیت شروع کنیم:بیایید بررسی کنیم که الان سلسله مراتب ذخیره سازی داخل SparceArray به چه شکلی شده: و در نهایت خروجی کار میشه :وظیفه متد ()saveHierarchyState  ریشه (root) اپلیکیشن اندرویدی یه پنجره به نام PhoneWindow هستش ، که این پنجره حاوی یه ریشه view  برای تمام رابط کاربری خودشه که بهش mContentParent می گن. در زمان مناسب، اندروید به پنجره PhoneWindow دستور می‌ده تا ()saveHierarchyState که به معنای ذخیره وضعیت رابط کاربری هست اجرا بشه ، سپس متدی با همین نام در ریشه view خودش فراخوانی می کنه. از این نقطه به بعد، ما در فرآیند ذخیره سازی وضعیت view هستیم ،ما نمی دونیم چه نوع view ای در ریشه پنجره PhoneWindow قرار داره، اما اجازه بدید برای لحظه ای فرض کنیم که فقط یه view اولیه داریم. در این صورت، زمانی که ()saveHierarchyState فراخوانی می شه، بلافاصله ()dispatchSaveInstanceState رو داخل خودش فراخوانی می کنه.سپس ()dispatchSaveInstanceState دو کار جالب انجام می ده:اول، ()onSaveInstanceState را داخل خودش فراخوانی می کنه . دوم: parcelable ای که از()onSaveInstanceState گرفته رو داخل یه SparseAarray به عنوان کانتینر قرار میده که توسط PhoneWindow در اختیارش قرار گرفته است.سپس PhoneWindow آرایه SparseArray برگشتی رو می گیره و در یک Bundle قرار میده، و در نهایت PhoneWindow اون Bundle را به سیستم اندروید برمی گردونه.  خیلی خوشحال میشم در صورتی که این نوشته رو دوست داشتید ❤️ کنید یا نظرتون را از طریق کامنت برام بنویسید.سورس مربوط به همین مقاله رو داخل گیت هابم اضافه کردم میتونید ازش استفاده کنید و اگه مشکلی داشت کمک کنید باهم درستش کنیم .(روی متن کلیک کنید)</description>
                <category>سعید هنری</category>
                <author>سعید هنری</author>
                <pubDate>Sun, 17 Jul 2022 22:42:19 +0430</pubDate>
            </item>
            </channel>
</rss>