عید امسال رو به خوردن و فیلم دیدن میگذروندم تا اینکه یه وظیفهای رو بهم محول کردن😃. اونم این بود که قیمت بلیت هواپیمارو توی یه سایتی چک کنم تا وقتی که خیلی ارزون شد، ازش بخریم. چند دفعهای توی سایت رفتم و قیمتهارو نگاه کردم ولی بعدش گفتم چکاریه که خودم چک کنم، بجاش یه اپ اندروید درست میکنم تا قیمت بلیت رو هر ۱۵ دقیقه بگیره و اگر از یه حدی ارزونتر بود، خبرم کنه.
چون پروژه برای خودم بود و وقت هم داشتم، هرچیزی که این یه مدت میخواستم امتحان کنم ولی نشده بود رو توی این پروژه استفاده کردم!😃 دو روزی ازم وقت گرفت ولی تجربهی خوبی بود، در ادامه چندتا نکته از این تجربه رو میگم. در ضمن سورس پروژه رو هم توی گیتهاب منتشر کردم تا شاید به عنوان نمونه سورس به کار کسی بیاد یا حتی بعدا خودم چیزهای دیگهای رو روش تست کنم. لینک ریپوی گیتهاب پروژه:
https://github.com/abbas-oveissi/Charter
نکاتی که میخواستم بگم اینا هست: ادامه …
اگر یادتون باشه آخرین نمونه پروژهای که توی گیتهاب گذاشتم (SingleAcitvityPattern) با زبان کاتلین نوشته شده بود. هدف اصلیم از این پروژه پیادهسازی الگوی Single Activity با استفاده از فرگمنتها و هدف فرعیم آشنایی بیشتر با کاتلین بود. قبلا در مورد هدف اصلی یه پست بلاگ نوشتم و الان میخوام در رابطه با هدف فرعی صحبت کنم. استفاده از کاتلین زمان بیشتری ازم گرفت ولی در عوض با نکات مختلفی آشنا شدم که در ادامه براتون تعریف میکنم.
ترکیب کاتلین و Dagger
دیروز چندساعتی درگیر این بودم که چطوری توی پروژه میتونم NavigationManager رو از طریق Dagger به فرگمنتها اینجکت کنم. با توجه به ایدهای که در مورد NavigationManager داشتم، میشه توی یه پروژه چندتا NavigationManager داشت که هرکدوم مدیریت تعدادی از فرگمنتهارو به عهده داشته باشه (اگر پست قبلی بلاگمو در مورد این پروژه خوندید و ایدهمو در رابطه با NavigationManager متوجه نشدید، دلیلش اینه توضیحش توی متن سخته! شاید بعدا یه ویدیو برای توضیحش درست کردم)، این ایده استفاده از Dagger برای اینجکت کردنو خیلی سخت کرده بود. البته توی این چند ساعت، درگیر دو تا مشکل دیگه هم داشتم.
اولین مشکل این بود که وقتی Dagger رو به پروژه اضافه کردم، موقع اجرا خطای NoClassDefFoundError میداد!!! آخرش بعد از کلی تلاش و سرچ کردن، فهمیدم مشکل از من نیست! مشکل از Dagger هست و نسخه جدید ۲٫۱۴٫۱ رو فقط به خاطر حل این مشکل ریلیز کردن.😑
مشکل دوم این بود که وقتی میخواستم بصورت همزمان دو انوتیشن @inject و یه qualifire به اسم @PerFragment رو برای یه فیلد استفاده کنم، برنامه کرش میکرد!! بعد از کلی دیباگ کردن و بررسی سورسهای جنریت شدهی Dagger، متوجه شدم qualifireیی که میذارم اصلا اثر نمیکنه، انگار وجود نداره!! در نهایت با سرچ کردن، فهمیدم اگر میخوام همچین کاری رو توی کاتلین بکنم، باید از یه انوتیشن به اسم @field استفاده کنم. وقتی کد رو به شکل زیر تغییر دادم، برنامه درست شد.
1 |
@Inject @field:PerFragment lateinit var navigationManager: NavigationManager |
این نکته رو از یه issue توی ریپوی Dagger یاد گرفتم. کلا داخل این issue در مورد Best Practiceهای استفاده از Dagger توی کاتلین صحبت میکنن. نکتههای جالبی داخلش هست.
https://github.com/google/dagger/issues/900
تفاوت onCreateView با onViewCreated
همون شبی که پروژه رو توی گیتهاب گذاشتم، شایان پوروطن لطف کرد و ۲ ۳ ساعتی در مورد جزییات پیادهسازی پروژه صحبت کردیم. جدای همه نکتههایی که ازش یاد گرفتم، یه نکته جالب و ساده هم یادم داد. قضیه این بود که من غر میزدم چرا نمیتونیم داخل متد onCreateView فرگمنت از قابلیت static layout imports کاتلین استفاده کنیم و مجبوریم بجاش کدمون رو توی متد onViewCreated بنویسم، اینجا بود که شایان بهم گفت خب درستش همینه و نه اونکاری که من تا الان میکردم😀
تا اون لحظه، هرچی سورس و مثال دیده بودم، همشون توی onCreateView کار findview رو انجام داده بودن و این باعث شده بود که فکرکنم راه درستش اینه!!! درصورتی که اشتباه بود. البته خیلی کم پیش میاد که این اشتباه باعث باگ بشه ولی بهرحال امکانش هست. دلیل اشتباه بودنش اینه که اگر layout پیچیده باشه، احتمال داره بعضی از viewها هنوز توی onCreateView ساخته نشده باشند!! برای اطمینان بیشتر باید هرکاری با view دارید، توی onViewCreated انجام بدید.
کشف ریپوهای کاتلینی-اندرویدی
چون هنوز کاتلین رو مسلط نیستم، تقریبا برای هرکاری باید توی گوگل بگردم. در حین همین گشتنها به یه اکانت organization جالب توی گیتهاب به اسم android رسیدم. دو نفر داخلش هستند، یکیش جیک وارتون هست. همین که جیک وارتون داخلشه، دیگه حجت رو بر من تموم کرد و همه ریپوهاشو بررسی کردم. دو تا ریپوی جالب پیدا کردم.
اولی android-ktx که extensionهای کاربردی اندروید هست.
https://github.com/android/android-ktx
دومی kotlin-guides که راهنمای استفاده از کاتلین در پروژههای اندروید هست.
توی پست قبلی در مورد الگوهای مختلف ساخت پروژههای اندروید صحبت کردم و خلاصهای از تحقیقاتمو نوشتم. خروجی اون تحقیقات یه پروژه تمرینی براساس الگوی “تک اکتیویتی، چند فرگمنت” شده که در ادامه بیشتر در موردش توضیح میدم و میتونید از طریق گیتهابم به سورسش دسترسی پیدا کنید.
توی این پروژه تمرکزم روی این قضیه بوده که چطوری جا به جایی بین صفحههای برنامه (همون فرگمنتها) رو مدیریت کنم. برای همین فعلا الگوهای معماری مثل MVP یا Dagger2 و RxJava رو قاطی پروژه نکردم. در ضمن از زبان Kotlin برای ساخت پروژه استفاده کردم تا با این زبان بیشتر آشنا بشم ولی بدیهی هس که هرکاری توی این پروژه کردم رو میشه خیلی راحت توی یه پروژهی جاوایی انجام داد.
چالشها
سعی کردم انواع چالشهایی که بخاطر استفاده از فرگمنتها توی پروژههای واقعی پیش میاد رو توی این پروژه پیاده کنم تا اگر پیادهسازیم مشکلی داره سریعتر متوجه بشم. چالشهایی که به ذهنم رسید این موارد بوده: (اگر چالش دیگهای به ذهنتون میرسه بهم بگید تا در موردش بیشتر صحبت کنیم)
۱- استفاده از BottomNavigationView: جدیدا در بیشتر برنامهها از Bottom Navigation استفاده میشه، به همین دلیل منم برای شروع این مدل نویگیشن رو انتخاب کردم. البته در آینده میشه مثال رو با navigation drawer هم پیاده کرد یا مدیریت backstack رو از حالتی که الان هست به حالتی شبیه اینستاگرام تغییر داد که هر تب برای خودش backstack جدا داشته باشه.
۲- مدیریت دکمهی Back: بصورت پیشفرض این دکمه رو اکتیویتی مدیریت میکنه ولی خیلی وقتا پیش میاد بعضی از فرگمنتها نیاز دارند که خودشون اینکارو به جای اکتیویتی میزبان مدیریت کنند.
۳- استفاده از Tab به همراه ViewPager در فرگمنت: گاهی پیش میاد یه فرگمنت داخلش تعدادی تب داره که هر تب خودش فرگمنت جدایی هس، توی این حالت مدیریت فرگمنتهای داخلی به عهدهی اداپتر ViewPager هست
۴- استفاده از فرگمنتی که داخلش تعدادی فرگمنت دیگه داره: در ظاهر میتونه شبیه حالت قبلی باشه ولی فرقش توی اینه مدیریت فرگمنتهای داخلی با فرگمنت والد هست.
۵- مدیریت ویوهای داخل اکتیویتی: گاهی توی اپلیکیشن نیاز هست وقتی کاربر روی آیتمی کلیک کرد، به جزییاتش بره. توی این حالت دیگه نیاز نیست Bottom Navigation رو ببینه یا شاید لازم باشه دکمهی FAB نشون داده بشه.
ایدهها
برای اینکه بتونم این چالشهای بالارو حل کنم، از ایدههای زیر استفاده کردم.
ایده اول – مدیریت جا به جایی صفحات یا همون navigation رو به یه کلاس به اسم NavigationManager سپردم. البته خیلی از مثالهای توی گیتهاب اینجوری بودند. اینکار باعث رعایت شدن اصل Single Responsibility که حرف S در اصول SOLID هست میشه. این کلاس برای مدیریت navigation نیاز به یدونه FragmentManager و شناسه لیوتی که قراره کانتینر فرگمنتها باشه داره. وقتی مدیریت navigation در یک کلاس جدا باشه، در آینده میشه مثلا بدون اینکه بقیه بخشهای برنامه رو خیلی تغییر داد، بجای backstack اندروید از یه backstack کاستوم استفاده کرد. درکل دست آدمو برای توسعههای بعدی باز میذاره.
ایده دوم- برای اینکه بتونم ساختار تو در تو بودن فرگمنتهارو در جاهایی که خود فرگمنت شامل تعدادی فرگمنت دیگه باشه رو مدیریت کنم، از پیادهسازی Dagger2 ایده گرفتم. ایده اینه هر کلاسی که اینترفیس HasNavigationManager رو پیادهسازی کرده باشه وظیفه داشته باشه فرگمنتهای داخلشو مدیریت کنه ( از ماژول اندروید Dagger2 و نحوهی کمک گرفتنش اینترفیسهای HasActivityInjector یا HasFragmentInjector الگو گرفتم). قضیه اینه هر فرگمنت وقتی داره لود میشه چک میکنه چه کسی مدیریتشو داره و ازش یه object از نوع NavigationManager میگیره تا کارهایی که نیاز داره رو انجام بده. اون کلاسی که اینترفیس HasNavigationManager رو پیاده میکنه، باید در پیادهسازی متد provideNavigationManager یه object از نوع NavigationManager برای فرگمنتهای که مدیریت میکنه فراهم کنه تا اونا به روشی که گفتم ازش استفاده کنند. اینکار باعث میشه چالش ۴ راحتتر حل بشه و مدیریت سلسله مراتبی باشه، در نتیجه اکتیویتی میزبان همهی این فرگمنتها خیلی شلوغ نشه!
ایده سوم- فرگمنتهایی که نیاز داشته باشند دکمهی back رو مدیریت کنند، میتونند متد onBackPressed رو که داخل BaseFragment هست، override کنند. اکتیوتی وقتی دکمهی بک زده میشه، چک میکنه فرگمنت جاری (من فرض کردم همیشه یه فرگمنت به عنوان فرگمنت جاری باشه! این فرض رو میشه در آینده عوض کرد. الان هر فرگمنتی که آخرین بار نشون داده شده باشه میشه فرگمنت جاری) میخواد بک رو هندل کنه یا نه؟! اگر کرد که اکتیویتی کاری نمیکنه و اگر نکرد، خود اکتیویتی کار همیشگیش رو انجام میده.
ایده چهارم- برای اینکه هر فرگمنت بتونه المانهای داخل اکتیویتی رو برای خودش مدیریت کنه، یه سری متد توی اینترفیس FragmentInteractionListener تعریف کردم. فرگمنتها برای ارتباط با اکتیویتی میزبان از اینترفیسهای خودشون مثل OnNotificationFragmentInteractionListener یا OnProfileFragmentInteractionListener استفاده میکنند که همشون از FragmentInteractionListener ارثبری میکنند، در نتیجه همه فرگمنتها میتونن متدهای داخل اینترفیس FragmentInteractionListener که اکتیویتی میزبانشون پیادهسازی کرده رو صدا بزنند.
در آخر تاکید کنم این پروژه تمرینی در حال توسعه هست و امکان داره جاهاییش اشکال داشته باشه یا حتی در آینده تغییر بکنه، در نتیجه با اینکه تلاشم اینه همهی BestPracticeهارو داخلش رعایت کنم ولی فعلا زوده که خیلی خیلی مطمئن از ساختارش توی پروژههاتون استفاده کنید.
میتونید سورس کامل پروژه رو توی گیتهابم و ریپوی SingleActivityPattern ببینید.
از وقتی اندروید رو شروع کردم، خیلی رابطهی خوبی با فرگمنت نداشتم و فقط جاهایی که مجبور بودم ازش استفاده کردم. تا الانم پیش نیومده پروژهای رو بطور کامل با فرگمنت درست کنم. چند وقته که خیلی وسوسه شدم به سمت استفادهی بیشتر از فرگمنت برم و دو سه روزی هست که دارم روی این موضوع تحقیق میکنم. سعی کردم مقالهها و سورسهای مختلفی که به این موضوع ربط دارند رو دونه دونه بخونم و روی فرقهاشون فکرکنم. البته در مورد فرگمنتها و مخصوصا FragmentManager هم بیشتر مطالعه کردم، تقریبا هیچ دیدی از نحوهی کار FragmentManager نداشتم ولی الان خیلی بهتر شدم😃. این پست بیشتر از اینکه قرار باشه الگوی خاصی رو تایید کنه یا قضیهای رو اثبات کنه، خلاصهای از تحقیقات شخصیم هس که در نهایت خروجیش یه پروژهی تمرینی با الگوی “تک اکتیویتی، چند فرگمنت” شده که چند روز آینده توی گیتهاب میذارم. ادامه …
اگر خیلی پروژهی اندروید روی لپتاپتون داشته باشید، کلی از هاردتون رو پر میکنند. البته بخشهای اصلی پروژه خیلی فضا نمیگیرند و اصولا ۸۰درصد از فضای اشغال شده بخاطر فولدرهای build هست. چند روز پیش دیدم که هاردم پر شده و هرچی گشتم چیزی پیدا نکردم که بشه پاک کرد تا هارد خالی بشه. واسه همین رفتم سراغ پروژههای اندرویدم تا فولدرهای build اون پروژههایی که دیگه خیلی ازشون استفاده نمیکنم رو پاک کنم. چندتایی رو پاک کردم اما دیدم خیلی طول میکشه😴 واسه همین تصمیم گرفتم به جای اینکه یه کار تکراری رو پشت هم انجام بدم، یه shell script بنویسم که همینکارو برام بکنه😎
بعدی کمی سرچ کردن، آخرش تونستم به نتیجهای که میخوام برسم. این اسکریپ که نوشتمو اگر توی فولدر پروژههاتون اجرا کنید، دونه دونه ازتون میپرسه که میخواید فولدرهای build کدوم پروژه رو پاک کنه! اگر y یا Y جواب بدید، همه فولدرهای build رو پاک میکنه😃 با این کار تونستم ۵گیگ هاردمو خالی کنم، بدون اینکه چیز مهمی رو پاک کنم!😃 لینک اسکریپت:
https://gist.github.com/abbas-oveissi/2d85b8178c11952ae8960392d834b03a
‼️نکتهی مهم: اگر میخواید اسکریپت رو تست کنید، حتما از پروژههاتون نسخهی پشتیبان داشته باشید. چون من تخصصی توی اینجور چیزا ندارم و تازه دارم یاد میگیرم😃یهو مشکلی برای پروژههاتون پیش نیاد!
در پست قبلی در مورد این صحبت کردیم که با چه رویکردهایی میشه سورس یک اپلیکیشن رو بررسی کرد. اولین اپلیکیشنی که برای بررسی انتخاب شده اسمش کیکاستارتر هست. اگر تا حالا اسم کیکستارتر رو نشنیدید، ویکی پدیا فارسی در موردش اینجوری توضیح میده:
کیکاستارتر (به انگلیسی: Kickstarter)، شرکتی آمریکایی و عامالمنفعه برای کمک به پروژههای نوآورانه در زندگی بشر است که از استارتآپهایی با ایدههای نوین و خلاقانه استقبال میکند. ایدههای جدید در همه زمینهها از فناوری گرفته تا آشپزی در وبسایت این شرکت برای جذب سرمایه عمومی تحت پوشش قرار میگیرد.
ما به سایت و اپلیکیشن iOSش کار نداریم و فقط سورس اندروید رو بررسی میکنیم. اگر دوس دارید اول خود اپلیکیشن رو ببنیید، میتونید از گوگل پلی دانلود کنید. سورس اپلیکیش اندروید کیک استارتر داخل این ریپو گیتهاب هست. برای بررسی سورس اپلیکیشن مجبور نیستید که سورس رو بیلد کنید یا حتی دانلودش بکنید ( البته معمولا دانلود کردن کار رو خیلی راحتتر میکنه). من خودم از سایت گیتهاب استفاده کردم و چیزی دانلود نکردم.
خب حالا وقتشه که کار رو شروع کنیم. در ادامه نکتههایی که از جاهای مختلف سورس کیک استارتر یادگرفتم رو مینویسم.
توجه: اشکال نداره اگر این نکتههایی که من نوشتم براتون خیلی سخت یا شاید حتی خیلی آسون باشه، اینها فقط نکتههایی هستند که من از بررسی سورس کیکاستارتر یاد گرفتم. حالا احتمال داره شما وقتی خودتون سورس رو بررسی کنید به نکتههای دیگهای توجه کنید یا چیزهای دیگهای بنظرتون جالب باشه.
در رابطه با فواید خوندن سورس پروژههایی (میتونه خروجیش اپلیکیشن باشه یا کتابخونه) که توسط افراد دیگه نوشته شده مقالههای زیادی توی اینترنت وجود داره و لازم به صحبت بیشتر نیست. فقط اگر بخوام تجربه شخصی خودمو بگم، فکرکنم در حدود ۶۰ ۷۰درصد چیزهایی که بلدم رو با خوندن سورسها یاد گرفتم. مهمترین بهونهای که بعضی برنامهنویسا میارن تا اینکار پرفایده رو انجام ندن، اینه که میگن هنوز دانششون برای اینکار کم هس. در صورتی که خب بدیهی هست مثلاً اگر منم سورس اپلیکیشن تلگرام رو بررسی میکنم قطعاً کلی از بخشهاشو متوجه نمیشم و هیچوقت هم هدفم این نیست ۱۰۰درصد یه پروژه رو متوجه بشم. سورسهارو میشه با دو رویکرد زیر بررسی کرد. ادامه …
برخلاف سری قبلی که خیلی طول کشید تا نسخهی جدیدی از حِلما منتشر بشه، ایندفعه توی کمتر از یک هفته نسخهی ۰٫۲٫۰ هم منتشر شد. دلیلش اینه که در نسخهی قبلی، داخل پروژه اندرویدی که حِلما تولید میکرد از قابلیتهای آخرین نسخهی کتابخونهی دگر۲ (نسخهی ۲.۱۱) استفاده نمیشد و نیاز بود تا حِلما بهروز بشه. مهمترین تغییر این نسخهی حلما استفاده از ماژول اندروید کتابخونه دگر هست که شکل تزریق وابستگیها به اکتیویتی و فرگمنت رو کاملا عوض میکنه. در ادامه میتونید لیست تغییرات جدید رو ببینید. ادامه …
اگر پروژههاتون رو براساس الگوی معماری MVP درست میکنید، احتمالا از ساخت فایلهای اضافه برای هر اکتیویتی یا فرگمنت خسته شدید. اینجاس جنریتور میتونه کلی از کارتون کم کنه! و این وظیفه رو به عهده بگیره. خودم از وقتی که نسخهی خیلی اولیه جنریتور رو منتشر کردم، ازش استفاده میکنم. مزیت جنریتور در ساخت فایلهای مرتبط با MVP و … نسبت به روشهای دیگه اینه هم سریعتر هست و هم اینکه مثل روش کپی پیست باگ تولید نمیکنه (مثلا پیش نمیاد یادتون بره یه فیلد رو rename کنید).
توی این مدت یه سری مشکلات داخلش پیدا کردم. بین پروژهها دو روزی فرصت شد تا برای بهبود و توسعهاش وقت بذارم. البته مهمترین مشکل نسخهی قبل نداشتن راهنما برای استفاده بود. در ادامه تغییرات جدید و نحوهی استفاده از جنریتور رو براتون توضیح میدم. ادامه …
در قسمت قبل نحوهی استفاده از Component.Builder@ و BindsInstance@ رو توضیح دادم، توی این پُست قراره در مورد دو نکتهی بعدی بنویسم. اگر میخواید به سورس کامل دسترسی داشته باشید به ریپوی SearchMovies برید.