بسم الله الرحمن الرحیم
از دیرباز برنامهنویسان دنبال این بوده اند که برای نوشتن اپلیکیشن فقط یک کد بنویسند و در دستگاهها و پلتفرمها و سیستم عاملهای مختلف همان را اجرا کنند. بعد از ظهور HTML5 در سال ۲۰۱۱ فناوریهای وب به عنوان روشی مهم برای تولید برنامههای کراسپلتفرم مورد توجه قرار گرفتند. خود حوزهی وب به شکل مستقیم به علت دسترسیهای محدود به امکانات سیستم عامل و... کافی به نظر نمیرسید. از این رو از همان اوایل ابزارهایی تولید شدند که این محدودیتها را از بین ببرند.
پس از HTML 5 ابزارهای بسیاری برای رفع محدودیتهای وب، و با دیدگاه استفاده از همان زبانهای HTML و CSS و JS برای تولید اپلکیشنهای موبایلی و دسکتاپی رونمایی شدند. در حوزهی تولید اپلیکیشنهای موبایلی ابزار PhoneGap که بعدا به Apache Cordova تغییر نام داد به وجود آمد، در حوزهی دسکتاپ هم ابزار Node-Webkit که بعدا به NW.js تغییر نام پیدا کرد. مدتی بعد شرکت گیتهاب ابزار Atom-Shell را با همین هدف تولید کرد که بعدا به Electron تغییر نام پیدا کرد. این ابزارها روز به روز پیشرفت کردهاند و امروزه به ابزارهای مدرن دیگری همچون Tauri و Capacitor رسیدهاند.
اپلیکیشنهایی که به این شکل و در اصل با فناوریهای وب تولید میشوند اصطلاحا اپلیکیشنهای هیبریدی نامیده میشوند (Hybrid App) و در مقابل اپلیکیشنهایی که با زبانهای بومی آن پلتفرم تولید میشوند قرار میگیرند (Native App).
از همان ابتدا روشهای هیبریدی به شدت مورد استقبال قرار گرفتند تا حدی که شرکت Intel نرم افزار Intel XDK را که یک IDE مخصوص تولید اپلیکیشنهای هیبریدی بود تولید کرد و خیلی کارهای دیگر انجام شد (از فناوری CrossWalk تا سایتهایی مثل Cocoon.io و...) که متأسفانه هماکنون تمامی این پروژهها رها شده اند.
از همان ابتدا اعتقاد داشتم که با روشهای هیبریدی میشود نرم افزارهای خیلی خوبی تولید کرد، و رها شدن پروژههای خوب حوزهی هیبرید خیلی برایم ناراحتکننده بود. شاید بتوان گفت یکی از دلایل ضعیف شدن ابزارهای هیبرید و آن شور و شوق اولیه ظهور PWAها و پروژهی فوگو بود، که صد البته فناوری جذابی است و خودم مدتهاست از طرفداران پر و پا قرص آن هستم.
من، مجتبی قاسم زاده تهرانی، تقریبا از حدود سال ۹۳ وارد حوزهی وب شدم، ولی از چند سال قبلش با ابزارهای هیبریدی آشنا بودم و جالب اینجاست که علت رغبت من به برنامهنویسی وب نیز همین بود. از چند سال پیش از ورودم به حوزهی وب که در زمینهی بازیسازی کار میکردم با ابزارهای NW.js و مشتقاتی از Cordova آشنا بودم و از آنها استفاده میکردم.
بعدها اپلیکیشن حفظیار و قرآن پروژکتور را با فناوریهای وب و با استفاده از ابزار Cordova به عنوان پروژهی کناره طراحی کردم.
تا اینکه امسال برای یک پروژهی دیگر تصمیم گرفتم به روش هیبریدی اپلیکیشن اندروید بسازم، ولی این بار با آن پروژههای کناره فرق میکرد! این بار تجربیاتی کسب کردم که دوست داشتم منتشر کنم. من برنامهنویس اندروید نیستم و زبان جاوا را نیز فقط در حد درس برنامهنویسی در دانشگاه یاد گرفتهام، ولی در حوزهی وب تخصصم خوب است، برای همین بهترین روش برای کسی مثل من تولید اپلیکیشن هیبریدی است.
برای تولید اپلیکیشن هیبرید اندرویدی سه راهکار اصلی وجود دارد:
ابزار Cordova را شاید بتوان قدیمیترین ابزار تولید اپ هیبریدی دانست که در سال ۲۰۰۹ تولید شده است.
کوردوا از فناوریهای امروزی جاوااسکریپت خیلی فاصله دارد. پلاگینهای کوردوا همه به سبک سنتی کار میکنند، همهی آنها پس از نصب آبجکتهایی را به صورت Global تعریف میکنند که بدون نیاز به import میتوان از آنها استفاده کرد. خیلی از پلاگینهای کوردوا تایپاسکریپتی نیستند و IDE نمیتواند خیلی خوب توابع آنها را پیشنهاد دهد. معمولاً همهی توابع کوردوایی به صورت Callback کار میکنند و با Promise کار نمیکنند!
یکی از مشکلات اصلی کوردوا این است که پکیجهای اصلی ساختهشده روی کورودا اکثرا رها شده اند و سالهاست که بروزرسانی نمیشوند، و خیلی وقتها با بروزرسانیهای نسخههای اندروید و... ناسازگار و غیر قابل استفاده میشوند.
البته ناگفته نماند که سازندگان فریمورک Ionic با توسعهی مجموعه پکیجی تحت عنوان awesome cordova plugins تا حد امکان این مشکل را مرتفع کردند، و روی بیشتر پلاگینهای مهم کوردوا یک wrapper نوشته اند که هم تایپاسکریپتی باشد، هم با import مورد استفاده قرار بگیرد و هم Promise برگرداند. این افراد بعدها به خاطر ساختار سنتی کوردوا و یک سری مسائل دیگر خودشان دست به تولید ابزار دیگری تحت عنوان Capacitor زدهاند که در حال حاضر رقیب اصلی و امروزی کوردوا به حساب میآید.
تیم سازندهی فریمورک Ionic سالها بر پایهی Cordova فریمورک خود را پیش میبردند، بعدها به علت کهنه بودن ساختار Cordova مجموعه پکیجهای Cordova awesome plugins را توسعه دادند، و بعدها به این فکر افتادند که جایگزین امروزیتر و مناسبتری از آن را خودشان طراحی کنند. این شد که کپسیتور متولد شد.
تفکر پشت کپسیتور این بود که ما در اصل یک برنامهی وبی پیشرو (PWA) بنویسیم و سپس از همان برنامه خروجی اپ اندروید و آیاواس بگیریم و در مارکتها منتشر کنیم. از این رو معمولا پلاگینهای کپسیتور هم وب را پشتیبانی میکنند و هم اندروید و هم iOS را.
کپسیتور ساختاری امروزی دارد، یکپارچگی خوبی با ابزارهای Build امروزی دارد. تمام پلاگینهایش به صورت importشدنی کار میکنند، پلاگینها را مجبور به استفاده از تایپاسکریپت میکند و در نتیجه تمام پلاگینهای رسمی و غیر رسمیاش تایپاسکریپتی هستند. علاوه بر این همهی آنها مبتنی بر Promise هستند و از ساختار callback در توابعش خبری نیست.
کپسیتور سازگاری بسیار خوبی با کوردوا و پلاگینهایش دارد و بسیاری از پلاگینهای کوردوا را میتوان به همان صورت در کپسیتور استفاده کرد.
مهمترین مشکل کپسیتور از نظر من این بود که طبق ادعای خودش فقط از Webview 60 به بالا را در اندروید پشتیبانی میکرد! و این اصلا خوب نبود. چون بسیاری از کاربران اندرویدهای ۵ یا ۶ نمیتوانند درست با اپلیکیشن ما کار کنند چون معمولا وبویوی قدیمی دارند و وبویو هم چیزی نیست که کاربر خودش همین جوری آپدیتش کند.
این ابزاری است که گوگل برای تبدیل یک سایت PWA به اپ اندروید برای انتشار در مارکتها تولید کرده است. این ابزار با استفاده از فناوری Trusted Web Activity که به اختصار TWA نامیده میشود درون اپ ما مرورگر کروم را بدون نمایش نوار ابزار مرورگر و... باز میکند. امّا محدود به apiهای مرورگر میشود و اگر کاری را نشود با یک PWA انجام داد با این اپ هم نمیشود انجام داد.
برای راحتی کار با BubbleWrap سایت pwabuilder.com ساخته شده است که با استفاده از آن و وارد کردن آدرس سایتتان اگر از نظر امکانات PWA مشکلی نداشته باشید، میتونید مستقیما خروجی apk از سایتتون بگیرید که بعدا در مارکتها منتشر کنید.
مشکل این ابزار این است که کاربر مقصد ما حتما باید نسخهی بروزی از کروم را روی گوشی خود نصب داشته باشد، وگرنه یا از Webview عادی استفاده میکند که خیلی محدود است و یا مستقیم کاربر را به مرورگر گوشی هدایت میکند که خیلی ضایع است!
به علت عدم پشتیبانی کپسیتور و BubbleWrap از وبویوهای قدیمی، Cordova را به عنوان ابزار اصلی تولید اپ هیبریدی انتخاب کردم. امّا این اول ماجرا بود و چالشها تازه یکی یکی آغاز خواهد شد!
در این مقاله به چالشهای مشترک اندروید مثل نصب Android Studio و Android SDK و مسائلی مثل sign کردن خروجی اندروید نمیپردازم، چرا که آموزش اینها در خود سایت این ابزارها موجود است و در این مقاله بیشتر میخواهم انتقال تجربیاتی را که خودم درگیر آن بودم در اختیار شما بگذارم.
با یک تست ساده و نصب شبیهساز اندروید ۵.۱ که حداقل نسخهی اندروید مورد پشتیبانی کوردوا بود متوجه شدم نسخهی وبویوی آن مبتنی بر کروم ۳۷ است. این را در نظر داشته باشید که ES6 در کروم ۵۱ پیادهسازیاش در مرورگر تکمیل شد و در کروم ۶۲ قابلیت import رسما وارد کروم شد. نسخه ۳۷ خیلی قدیمی بود!
تست واقعی برنامه در مرورگر قدیمی:
برای این کار یک نسخهی قدیمی از NW.js را دانلود کردم و از آن به عنوان یک مرورگر برای تست کروم ۳۷ استفاده کردم!
شما در این لینک میتوانید تمام نسخههای قدیمی NW.js را که قبلا node-webkit نام داشت ببینید و دانلود کنید. هر نسخه هم دقیقا مشخص است که مبتنی بر کدام نسخهی کروم کار میکند، مثلا نسخهی v0.10.5 به نظر مناسب میرسید:
https://github.com/nwjs/nw.js/wiki/Downloads-of-old-versions
ابزار بیلد:
به شخصه Vite را دوست دارم، ولی هر بار میخواهم از آن استفاده کنم معمولا یک نیازمندی باعث میشود نتوانم، چون حتی با نصب Vite plugin legacy و با تنظیم درست browserslist خروجی کار در کروم ۳۷ (همان nw.js قدیمی) درست اجرا نمیشد. از این رو از Webpack خام استفاده کردم و Babel که انصافا خوب کار میکرد. فراموش نکنید که باید تنظیم browserslist را هم درست انجام دهید که این نسخه از کروم را پشتیبانی کند.
البته خیلی از پکیجها برای مرورگرهایی که تا این حد قدیمی باشند بیلد نشده اند، برای همین در تنظیمات وبپک برای این پکیجها باید استثنا قائل شویم که Babel کد آنها را نیز از داخل node_modules بخواند و با کروم ۳۷ سازگار کند!
نکتهی مهم دیگر هم اینکه از این به بعد برای استفاده از هر API باید یک چشممان به caniuse.com باشد که مطمئن شویم پشتیبانی خوبی از مرورگرهای قدیمی داریم.
فریمورک
من به شخصه با فریمورک Vue.js و Solid.js و تا حدی Svelte کار میکنم! فریمورکهای امروزی همچون Solid.js یا Vue 3 با مرورگرهای قدیمی ناسازگارند. پس اگر بخواهیم مرورگرهای قدیمی را پشتیبانی کنیم باید از فریمورکهای قدیمی همچون React یا Vue 2 استفاده کنیم.
از طرفی Vue 2.7 قابلیت script setup را اضافه کرده است، برای همین میتوان از این فریمورک استفاده کرد با این دیدگاه که از همین ابتدا تفاوتهای Vue 2 و Vue 3 را در نظر بگیریم که بعدها در صورت تصمیم به مهاجرت تا حد امکان کد کمتری تغییر کند.
سیاساس
با ظهور روشهای اتمیک برای نوشتن سیاساس مثل tailwind قصد ندارم از روشهای سنتی استفاده کنم. ولی متأسفانه tailwind علاقهی زیادی به استفاده از متغیرهای CSS دارد که در مرورگرهای قدیمی قابل استفاده نیست.
برای همین از فریمورک Uno.css استفاده کردم که به شدت شبیه به tailwind است، ولی با چند تا Regex ساده میشود کانفیگش کرد! برای همین مواردی مثل رنگ و پسزمینه و transform را خودم با regexهایی کانفیگ کردم که داخلشان از متغیر css استفاده نشود. مشکل بعدی این بود که uno.css ساختهی یکی از اعضای اصلی تیم Vite است، برای همین خیلی با وبپک سازگار نیست و چالشهایی با آن دارد، مثل ناسازگاری با کش دائمی وبپک و نگذشتن کدهای تولیدی آن از PostCSS. ولی هر طور بود مجبور بودیم با آن کنار بیاییم.
پلاگینهای کوردوا اکثرا خیلی قدیمی هستند، اکثر آنها سالهاست که رها شده اند. برخی کلا کار نمیکنند و باید forkهای بروزترشان را پیدا کرد و از آنها استفاده کرد. این مسأله خیلی حاد نبود تا اینکه خواستم Target SDK برنامه را آپدیت کنم که طبق استانداردهای اندروید و گوگلپلی این کار لازم است.
پس از ارتقا به SDK 32 اندروید پروژه از کار افتاد! میتوانم بگویم نیمی از پلاگینهای مورد استفاده خراب شد و مجبور شدم حذفشان کنم تا پروژه Build بشود، برخی دیگر هم جلوی بیلد را نگرفتند ولی به هر حال درست کار نمیکردند! حل مشکل خیلی سخت بود، مجبور شدیم دنبال forkهایی از پلاگینها بگردیم که مشکل را رفع کردهاند، برخی را مستقیما از روی گیتهابشان نصب کردیم و برای برخی جایگزین پیدا نکردیم. این مشکل فقط مربوط به پلاگینهای غیر رسمی نبود، حتی برخی پلاگینهای رسمی کوردوا مثل پلاگین StatusBar با اندروید جدید ناسازگار بود و امکان تغییر رنگ متن نوار وضعیت از کار افتاده بود. یا اینکه امکان نمایش SplashScreen در وسط کار که قبلا وجود داشت دیگر کار نمیکرد.
بعضی پکیجها جلوی بیلد apk را نمیگرفتند، ولی در صورت اجرا برنامه کرش میکرد و کاربر را به بیرون پرتاب میکرد. برای شناسایی اینکه چه پکیجی باعث این جور مشکلات میشود از ابزار Flipper استفاده کردم که علت کرش برنامههای اندروید را میتواند گزارش دهد.
وقتی دیدم که کوردوا تا این حد منسوخ شده است و maintain نمیشود فهمیدم اشتباه کردم که آن را انتخاب کردم و باید از همان اول سراغ Capacitor میرفتیم.
پلاگین Local notification، پلاگین StatusBar و NavigationBar و تم حالت روشن و تیره از مواردی بودند که با این بروزرسانی خراب شدند. برای برخی جایگزین پیدا کردم (مثل نوتیفیکیشن و تم روشن و تیره) و برای برخی دیگر نه.
ابزار کپسیتور با این دیدگاه ساخته شده است که ما در اصل یک PWA داریم و حالا میخواهیم یک نسخه اندروید یا iOS هم از آن داشته باشیم. پلاگینهای آن معمولا سعی میکنند که حتی پلتفرم وب را نیز تا حد امکان با APIهای فوگو پوشش دهند. تیمی که پشت فریمورک Ionicهستند آن را توسعه داده و نگهداری میکنند که تیم پویا و فعال و خوبی به نظر میرسند. پلاگینهای آن معمولا خیلی قدیمی نیست. یکپارچکی راحتتری با لایهی Native دارد و در کل از دور که نگاه میکنیم همه چیز گل و بلبل است!
از دور که نگاه میکنیم تنها مشکل کپسیتور عدم پشتیبانی از مرورگرهای قدیمی است (وبویوی زیر ۶۰) که نهایتا برای این افراد میتوانیم صفحهای جایگزین نشان دهیم و هدایتشان کنیم به جایی که وبویوی خود را بروز کنند. بعد از آن حتی از دست چالشهایی که برای مرورگرهای قدیمی متحمل میشدیم نیز خلاص خواهیم شد.
قانون پایستگی چالشها میگوید چالشها از بین نمیروند و از حالتی به حالت دیگر تبدیل میشوند ? با مهاجرت به کپسیتور از شر چالشهای کوردوا خلاص شدم و با چالشهای کپسیتور آشنا شدم.
کپسیتور در تنظیمات خود آدرس یک صفحهی HTML را میگیرد که در صورت وقوع خطا آن صفحه را باز کند. این خطا ممکن است از هر چیزی ناشی شود، ولی مهمترین علت آن میتواند قدیمی بودن وبویوی کاربر باشد. از طرفی چون قرار نیست کپسیتور این حالت را پشتیبانی کند در این صفحه بسیار محدود هستیم و نمیتوانیم پلاگینهای کپسیتور را استفاده کنیم یا حتی از آنجا به سایر صفحات ارجاع دهیم.
صفحهای طراحی کردم مبنی بر اینکه کاربر باید وبویوی خود را بروزرسانی کند تا بتواند از برنامهی ما استفاده کند. ولی طبیعتا اگر منشأ خطا چیز دیگری باشد نباید چنین پیامی نشان بدهیم! پس در این صفحه user agent کاربر را چک کردم که اگر وبویوی او قدیمی نباشد او را به صفحهی خطای دیگری ارجاع دهیم.
امّا مشکل این جا بود که کپسیتور طوری طراحی شده است که کلا امکان فرار از این صفحه را نمیدهد و پلاگینهایش نیز در این صفحه کار نمیکند! یعنی سازندگان کپسیتور چنین مسألهی مهمی را در نظر نگرفته بودند، در اینجا مجبور شدم به کدهای جاوای کپسیتور دست ببرم و آن شرط کذایی را پیدا کنم و تغییر دهم (به جای اینکه بررسی کند که آیا در صفحهی ارور هستیم، بررسی شود که آیا وبویو قدیمی است یا نه، چرا که در وبویوهای جدید دلیلی ندارد پلاگینها را غیر فعال کنیم). من برای اینکه نمیخواستم کد Native اندروید بزنم به ابزارهای هیبریدی روی آورده بودم، و اینکه چنین موضوع مهمی را در نظر نگرفته بودند اصلا جالب نبود.
کد را اصلاح کردم و در گیتهاب برایشان Pull request هم زدم. ولی تا قبل اینکه merge شود و نسخهی بعدی کپسیتور منتشر شود چه کنیم؟
برای این کار پکیجمنیجیرهای pnpm و yarn 2 به بالا قابلیت patch کردن پکیجها را دارند. در npm و yarn classic هم میتوانیم از پکیجی تحت عنوان patch-package استفاده کنیم. با این ابزار میتوانیم پکیجهای داخل node_modules را سیخ بزنیم! و تغییرات انجام شده روی آن پکیجها را در گیت کامیت کنیم که برای همهی اعضای پروژه اعمال شود.
مشکل بعدی کپسیتور این بود که از سرویسورکر پشتیبانی نمیکرد. اگر صفحهی ما از طریق سرویسورکر فرستاده میشد کپسیتور پروژه را یک پروژهی وبی معمولی تشخیص میداد!
بعد از کمی جستجو معلوم شد دلیلش این است که کپسیتور برای اینکه درست کار کند یک مقدار کد جاوااسکریپت را به صفحه تزریق میکند، ولی سرویسورکر خودش فایل را میفرستد و آن را به کپسیتور نمیدهد که بخواهد کد مورد نظر خود را در آن تزریق کند. در کوردوا این مشکل را نداشتیم! چرا که کوردوا اصولش بر این بود که باید یک تگ اسکریپت <script src=cordova.js> را درون فایل HTML خود قرار دهیم. این فایل را کوردوا خود به خود درون apk میاندازد و پس از آن کوردوا آمادهی استفاده است. ولی در کپسیتور برای اینکه مثلا کار برنامهنویس را راحت تر کند کد مورد نظر را در صفحه تزریق میکند که این باعث ناسازگاریاش با سرویسورکر شده بود!
سرویسورکر برایم خیلی مهم بود و نمیتوانستم به راحتی از کنارش بگذرم! از این رو دوباره دست به تغییر کدهای جاوای کپسیتور شدم و روالی مشابه Cordova در آن پیاده کردم. در کدهای جاوای کپسیتور گشتم و جایی را که کدهای جاوااسکریپت را به صفحه تزریق میکرد پیدا و حذف کردم. به جای آن یک آدرس مجازی ساختم که در صورت ریکوئست زدن به آن همان کدهایی که میخواست تزریق شود فرستاده شود. بعد مشابه کوردوا یک تگ اسکریپت در صفحه گذاشتم و آن آدرس مجازی را به آن دادم. کار سختی بود برای کسی که به نیتیو اندروید مسلط نیست، ولی به هر حال خدا را شکر مشکل سرویسورکر هم حل شد!
در پچ فوق چیزی که گفتم را اعمال کردهام. کپسیتور برای سازگاری با کوردوا در ریسپانس فایل cordova.js یک ریسپانس خالی میفرستاد که همان را با کدی که باید در صفحه تزریق میکرد جایگزین کردم و در صفحه تگ اسکریپت cordova.js را قرار دادم.
مشکل بعدی کپسیتور در ساین کردن فایل خروجی بود! کپسیتور در صورت استفاده از flavorهای اندروید مسیر فایل خروجی apk را گم میکند و نمیتواند آن را ساین کند! برای حل این مشکل باز هم مجبور شدم کدهای خود کپسیتور را دستکاری کنم و مسیر خروجی را اصلاح کنم.
بعد ساین انجام میشد ولی در برخی گوشیها قابل نصب نبود! مقداری جستجو کردم و متوجه شدم که برای ساین apk باید از ابزار apksigner استفاده شود، ولی کپسیتور از jarsigner استفاده میکند که آن فقط برای فایلهای aab است! باز هم کدهای کپسیتور را دستکاری کردم که از apksigner استفاده کند.
در مرحلهی بعدی ساین انجام میشد و در همهی گوشیها هم قابل نصب و درست بود، ولی فهمیدم کپسیتور فایل تنظیمات خودش را که اتفاقا شامل رمز کلید ساین برنامه است درون خود apk قرار میدهد! برای حل این مشکل نباید رمز کلید ساین برنامه را به کپسیتور بدهیم و باید با استفاده از همان خط فرمان در زمان بیلد مشخصات ساین را به کپسیتور بدهیم که برنامه را ساین کند.
بعد از انتشار برنامه، متوجه شدیم که در گوشیهای هواوی جدید بالا نمیآید! بعد از کلی بررسی نهایتا متوجه شدیم هواوی از وبویوی پیشفرض اندروید که متعلق به گوگل است استفاده نمیکند و وبویوی خودش را دارد. از طرفی سیستم نسخهبندی وبویوی هواوی مشابه گوگل نیست. مثلا در حال حاضر آخرین نسخهی وبویوی هواوی ۱۱ است، در حالی که کپسیتور وبویوهای قدیمی (زیر ۶۰) را قرار نیست پشتیبانی کند. ولی وبویوی ۱۱ هواوی اصلا قدیمی نیست و این تفاوت نسخهبندی باید در نظر گرفته میشد!
نهایتا این مشکل هم با دست بردن به کدهای جاوای کپسیتور رفع شد.
نتیجهی اخلاقی اینکه هیچگاه گول تبلیغات را نخورید. من قبل از استفاده از این ابزارها جور دیگری رویشان حساب میکردم، اما در این مدتی که به صورت واقعا عملیاتی از آنها استفاده کردم متوجه شدم متأسفانه هنوز خیلی کار دارند. حوزهی تولید اپلیکیشنهای هیبریدی موبایلی نه تنها در کوردوا و کپسیتور که حتی در ابزارهای Nativeتری همچون ReactNative چالشهای زیادی دارد، و اگر وارد این حوزهها میشوید باید درگیر شدن با کدهای Native را به جان بخرید!
من مشکلات و راهکارها را به طور خلاصه اشاره کردم و توضیح دادم، ولی خودم برای تشخیص اینکه اصلا این مشکلات از کجا ناشی میشود و یافتن این راهکار خیلی تلاش کردم، و برای پرداخت زکات چیزهایی که یاد گرفتم اینها را اینجا نوشتم. انشاءالله که دعا برای بندهی حقیر را فراموش نکنید.
والسلام علی من اتبع الهدی