Kriptofolio app series - Part 3
নতুন অ্যাপ বানানোর শুরুতে সবচেয়ে গুরুত্বপূর্ণ বিষয় হলো আর্কিটেকচার। সবচেয়ে বড় ভুল হলো কোনো আর্কিটেকচার স্টাইল ছাড়াই শুরু করা।
আর্কিটেকচার চয়েন বিষয়টি gần বছরে Android কমিউনিটিতে খুব বিবাদিত। এমনকি Google-ও জড়িয়ে পড়ল। 2017 সালে তারা তাদের নিজস্ব স্ট্যান্ডার্ডাইজড আর্কিটেকচার পদ্ধতি প্রস্তাব করল Android Architecture Components রিলিজ করে। এটি ডেভেলপারদের জীবন সহজ করার উদ্দেশ্যে ছিল।
এই পোস্টে, আমি প্রথমে আলোচনা করব কেন আমাদের অ্যাপ আর্কিটেক্ট করতে হয়। আমাদের কী অপশন আছে। তারপর শিখব কীভাবে করব। চাকা নতুন করে আবিষ্কার না করে, আমরা Android টিমের প্রদত্ত গাইডলাইন ব্যবহার করব।
এই পোস্ট লেখা আমার জন্য সবচেয়ে কঠিন ছিল আমার নিজের জ্ঞান অভাবের কারণে। প্রথমে আমাকে আর্কিটেকচার টপিকটি খুব ভালোভাবে অধ্যয়ন করতে হয় বড় চিত্র देखने জন্য। এখন আমি আমার ফাইন্ডিংস শেয়ার করতে প্রস্তুত।
Series content
- Introduction: A roadmap to build a modern Android app in 2018–2019
- Part 1: An introduction to the SOLID principles
- Part 2: How to start building your Android app: creating Mockups, UI, and XML layouts
- Part 3: All about that architecture: exploring different architecture patterns and how to use them in your app (you’re here)
- Part 4: How to implement Dependency Injection in your app with Dagger 2
- Part 5: Handle RESTful Web Services using Retrofit, OkHttp, Gson, Glide and Coroutines
কেন আপনাকে অ্যাপ আর্কিটেকচার সম্পর্কে চিন্তা করা উচিত?
সাধারণত যখন আপনি Android-এ কাজ করা শুরু করেন, আপনি বেশিরভাগ কোর বিজনেস লজিক অ্যাক্টিভিটি বা ফ্র্যাগমেন্টে লিখে ফেলেন। এটি সব নতুন Android ডেভেলপারদের ঘটে, আমারও সহিত। সব ছোট টিউটোরিয়াল এবং স্যাম্পল সেটি করার পরামর্শ দেয়। এবং আসলে, ছোট অ্যাপ্লিকেশন যা এক্সপ্লানেশনের জন্য তৈরি হয়, সেটি খুব ভালো কাজ করে।
তবে, চেষ্টা করুন একটি রিয়েল অ্যাপে যা دائمًا ইউজারের চাহিদা অনুযায়ী পরিবর্তন হচ্ছে এবং নতুন ফিচারের সাথে এক্সপ্যান্ড হচ্ছে। দ্রুত আপনি দেখবেন আপনার কোডিং এক্সপেরিয়েন্স আরও কষ্টকর হচ্ছে। সব কিছু তथাকথিত "God Classes" যেমন অ্যাক্টিভিটি বা ফ্র্যাগমেন্ট দ্বারা ম্যানেজ হচ্ছে। এগুলোর কোড লাইন তত বেশি যে আপনি সহজে হারিয়ে যান।
বেসিক্যালি আপনার সকল কোড স্পাঘেটির মত হয়ে যায় যেখানে সব কিছু মিক্সড থাকে। সকল অংশ একে অপরের ওপর নির্ভরশীল। তারপর যখন বিজনেস নতুন পরিবর্তন চায়, আপনার কাছে পুরো প্রজেক্ট রিবিল্ড করার अलावा কোনো চয়েস নেই। এটিই সেটি পয়েন্ট যেখানে আর্কিটেকচার সম্পর্কে প্রশ্নগুলো উঠে।
আপনার কোড স্ট্রাকচার করার আরেকটি ভালো পথ কি আছে?
অবশ্যই আছে! হাই কোয়ালিটি কোডের চাবি হলো SOLID প্রিন্সিপল ফলো করা। আমি এই সম্পর্কে আমার আগের পোস্টে বলেছিলাম (কারণ ছাড়া নয়)। আপনাকে separation of concerns-এর জন্য কিছু আর্কিটেকচার প্যাটার্ন অ্যাপ্লাই করতে হবে। আসলে, separation of concerns আপনার চূড়ান্ত লক্ষ্য হওয়া উচিত। এটি কোড কোয়ালিটি ইন্ডিকেট করার সবচেয়ে σημαντিক পয়েন্ট। অ্যাপ আর্কিটেকচারের জন্য khá কিছু প্যাটার্ন আছে। সবচেয়ে পরিচিত তিনটি ক্লাসিক টিয়ার আর্কিটেকচার হলো:
- MVC: Model-View-Controller
- MVP: Model-View-Presenter
- MVVM: Model-View-ViewModel
এই সকল প্যাটার্ন মূল একই আইডিয়া রিপ্রেজেন্ট করে — আপনার প্রজেক্টের কোডকে এমনভাবে স্ট্রাকচার করা যাতে সেটি আলাদা জেনেরিক লেয়ার দ্বারা পৃথক হয়। প্রতিটি লেয়ারের নিজস্ব দায়বদ্ধতা থাকে। তাই আপনার প্রজেক্ট মডুলার হয়: পৃথক কোড অংশগুলো আরও টেস্টেবল, এবং আপনার অ্যাপ সততা পরিবর্তনের জন্য ফ্লেক্সিবল।
যদি আমরা প্রতিটি প্যাটার্ন আলাদা আলাদা আলোচনা করি, তবে টপিকটি খুব ব্রড হয়ে যাবে। আমি শুধু আপনাকে প্রতিটি থেকে পরিচয় করিয়ে দিব যাতে আপনি মূল পার্থক্যগুলো বুঝতে পারেন।
The Model-View-Controller (MVC) pattern
এই প্যাটার্ন পুরনো দিনে Android অ্যাপ আর্কিটেকচারের প্রথম ইটেরেশন ছিল। এটি পরামর্শ দেয় আপনার কোডকে ৩টি আলাদা লেয়ারে পৃথক করার জন্য:
Model — ডেটা লেয়ার। বিজনেস লজিক হ্যান্ডেল করার এবং নেটওয়ার্ক ও ডাটাবেস লেয়ারের সাথে কমিউনিকেশনের জন্য দায়বদ্ধ।
View — ইউজার ইন্টারফেস (UI) লেয়ার। এটি Model থেকে ডেটার একটি সিম্পল ভিজ্যুয়ালাইজেশন।
Controller — লজিক লেয়ার, ইউজার আচরণের নোটিফিকেশন পায় এবং প্রয়োজন অনুযায়ী Model আপডেট করে।
এটি MVC স্কিমা। এতে আমরা দেখি Controller এবং View উভয় Model-এর ওপর নির্ভরশীল। Controller ডেটা আপডেট করে। View ডেটা পায়। যাইহোক, Model পৃথক এবং UI থেকে স্বাধীনভাবে টেস্ট করা যায়।
MVC প্যাটার্ন অ্যাপ্লাই করার কিছু অপ্রোচ আছে। এটি khá কনফিউজিং।
একটি হচ্ছে যখন অ্যাক্টিভিটি এবং ফ্র্যাগমেন্ট Controller-এর মতো কাজ করে। সেগুলো ডেটা প্রসেসিং এবং ভিউ আপডেট করার দায়বদ্ধ। এই আর্কিটেকচার অপ্রোচের সমস্যা হলো অ্যাক্টিভিটি এবং ফ্র্যাগমেন্ট খুব বড় এবং টেস্ট করা খুব কঠিন হয়ে যায়।
একটি অন্য অপ্রোচ যা আরও লজিক্যাল (এবং সঠিক) লাগে সেটি হলো অ্যাক্টিভিটি এবং ফ্র্যাগমেন্ট MVC জগতে View হওয়া উচিত। Controller আলাদা ক্লাস হওয়া উচিত যেগুলো কোনো Android ক্লাস এক্সটেন্ড বা ব্যবহার করে না। Models-র জন্যও একই।
যাইহোক আপনি MVC সম্পর্কে আরও 조사 করলে পাবেন যে Android প্রজেক্টে অ্যাপ্লাই করলে, সঠিক উপায়েও কোড লেয়ারগুলো একে অপরের ওপর নির্ভরশীল। তাই আমি রিকমেন্ড করব না আপনি আপনার পরবর্তী Android অ্যাপের জন্য এটি ব্যবহার করতে।
The Model-View-Presenter (MVP) pattern
প্রথম অপ্রোচ কাজ না করার পর, Android ডেভেলপাররা আরো একটু অগে বড়ল এবং সবচেয়ে জনপ্রিয় আর্কিটেকচার প্যাটার্নের একটি — MVP ব্যবহার করার চেষ্টা করল। এই প্যাটার্ন আর্কিটেকচার চয়েনের সেকেন্ড ইটেরেশন রিপ্রেজেন্ট করে। এই প্যাটার্ন ব্যাপকভাবে ব্যবহৃত হয়েছে এবং এখনও একটি রিকমেন্ডেড প্যাটার্ন। যারা Android ডেভেলপমেন্ট শুরু করছেন তাদের জন্য এটি শিখা সহজ। এর ৩টি আলাদা লেয়ারের রোল দেখা যাক:
Model — ডেটা লেয়ার, MVC প্যাটার্নের মতো একই। বিজনেস লজিক হ্যান্ডেল করার এবং নেটওয়ার্ক ও ডাটাবেস লেয়ারের সাথে কমিউনিকেশনের জন্য দায়বদ্ধ।
View — ইউজার ইন্টারফেস (UI) লেয়ার। ডেটা ডিসপ্লে করে এবং Presenter-কে ইউজার অ্যাকশন সম্পর্কে নোটিফাই করে।
Presenter — Model থেকে ডেটা রিট্রিভ করে, UI লজিক অ্যাপ্লাই করে এবং View-এর স্টেট ম্যানেজ করে, কী ডিসপ্লে করা উচিত সেটি ডিসাইড করে, এবং View থেকে ইউজার ইনপুট নোটিফিকেশনের প্রতি রিয়্যাক্ট করে। এটি মূলত MVC-এর Controller ছাড়া এটা View-এর সাথে কখনো tied নয়, শুধু একটি ইন্টারফেস।
MVP স্কিমা দেখায় View এবং Presenter ঘনিষ্ঠভাবে সম্পর্কিত। তাদের একে অপরের রেফারেন্স থাকতে হয়। তাদের সম্পর্ক একটি Contract ইন্টারফেস ক্লাসে ডিফাইন করা হয়।
এই প্যাটার্নের একটি উল্লেখযোগ্য কিন্তু কন্ট্রোলযোগ্য ডিসঅডভান্টেজ আছে। Presenter tendencia থাকে একটি বিশাল all-knowing ক্লাসে পরিণত হতে যদি আপনি যথেষ্ট কেয়ারফুল না হন এবং সিংগেল রেসপন্সিবিলিটি প্রিন্সিপল অনুযায়ী কোড ব্রেক না করেন। যাইহোক, সাধারণভাবে কথা বললে, MVP প্যাটার্ন খুব ভালো separation of concerns অফার করে। এটি আপনার প্রজেক্টের জন্য মেইন চয়েস হতে পারে।
The Model-View-ViewModel (MVVM) pattern
MVVM প্যাটার্ন অপ্রóch-এর থার্ড ইটেরেশন। এটি Android Architecture Components রিলিজের সাথে Android টিম দ্বারা রিকমেন্ডেড আর্কিটেকচার প্যাটার্ন হয়ে উঠল। তাই আমরা এই প্যাটার্ন শিখতে সবচেয়ে বেশি ফোকাস করব। "My Crypto Coins" অ্যাপের জন্যও আমি এটি ব্যবহার করব। আগের মত, এর আলাদা কোড লেয়ারগুলো দেখা যাক:
Model — ডেটা সোর্সকে অ্যাবস্ট্রাক্ট করে। ViewModel Model-র সাথে কাজ করে ডেটা পাওয়ার এবং সেভ করার জন্য।
View — যা ViewModel-কে ইউজারদের অ্যাকশন সম্পর্কে ইনফর্ম করে।
ViewModel — View-র জন্য প্রাসঙ্গিক ডেটা স্ট্রিম এক্সপোজ করে।
MVP প্যাটার্নের সাথে পার্থক্য হলো, MVVM-এ, ViewModel View-এর রেফারেন্স ধরে রাখে না যেমন Presenter রাখে। MVVM-এ, ViewModel ইভেন্টের একটি স্ট্রিম এক্সপোজ করে যাতে বিভিন্ন View বাইন্ড করতে পারে। অন্যদিকে, MVP-এ, Presenter সরাসরি View-কে বলে কী ডিসপ্লে করতে হবে। MVVM স্কিমা দেখা যাক:
MVVM-এ View-র ViewModel-এ রেফারেন্স আছে। ViewModel-কে View সম্পর্কে কোনো ইনফো নেই। View এবং ViewModel-এর মধ্যে many-to-one রিলেশনশিপ আছে।
Comparison of MVC vs MVP vs MVVM
এখানে একটি টেবিল যা আমি আলোচনা করা সব প্যাটার্ন সারমর্ম দেয়:
যে লক্ষ্য করতে পারলেন, MVC, MVP এবং MVVM-এর সাথে তুলনায় মডুলার ও টেস্টেবল মডার্ন অ্যাপ বানাতে उतনা ভালো নয়। কিন্তু প্রতিটি প্যাটার্নের নিজস্ব সুবিধা ও অসুবিধা আছে। আপনার চাহিদা পুর্টি পূরণ করলে সেটি ভালো চয়েস। আমি পরামর্শ দিই আপনি সব প্যাটার্ন আরও ইনভেস্টিগেট ও শিখুন কারণ এটা ارزش।
মéz, আমি আমার প্রজেক্ট চালু রাখব 2018-এর ট্রেন্ডিং প্যাটার্ন যা Google পুষ্টি করছে — MVVM।
Android Architecture Components
আপনি যদি Android অ্যাপ্লিকেশন লাইফসাইকেলের সাথে পরিচিত হন, তবে জানেন কীভাবে একটি অ্যাপ বানানো যাতে সব ডেটা ফ্লো সমস্যা, পারসিস্টেন্স এবং স্ট্যাবিলিটি ইস্যু এড়ানো যায় সাধারণত কনফিগারেশন চেঞ্জের সময়।
2017 সালে, Android টিম ডিসাইড করল যে আমরা যথেষ্ট স্ট্রাগল করেছি। তারা দায়িত্ব নিল এবং Android Architecture Components ফ্রেমওয়ার্ক পরিচয় করাল। এটি আপনাকে আপনার কোড কমপ্লিকেট করা ছাড়া বা হ্যাকস অ্যাপ্লাই করা ছাড়া সব সমস্যা সমাধান করতে দেয়।
Android Architecture Components হলো লাইব্রেরির একটি কালেকশন যা আপনাকে রোবাস্ট, টেস্টেবল, এবং মেইনটেইনেবল অ্যাপ ডিজাইন করতে সাহায্য করে। বর্তমানে যখন আমি এই ব্লগ পোস্ট লিখছি, এতে এই কম্পোনেন্টগুলো থাকে:
- Data Binding — ডিক্লেরেটিভলি অবজার্ভেবল ডেটাকে UI এলিমেন্টের সাথে বাইন্ড করুন
- Lifecycles — আপনার অ্যাক্টিভিটি এবং ফ্র্যাগমেন্ট লাইফসাইকেল ম্যানেজ করুন
- LiveData — আন্ডারলাইং ডাটাবেস পরিবর্তন হলে ভিউকে নোটিফাই করুন
- Navigation — ইন-অ্যাপ নেভিগেশনের জন্য প্রয়োজনীয় সব কিছু হ্যান্ডেল করুন
- Paging — আপনার ডেটা সোর্স থেকে ডিমান্ড অনুযায়ী আস্তে আস্তে ইনফরমেশন লোড করুন
- Room — ফ্লুয়েন্ট SQLite ডাটাবেস অ্যাক্সেস
- ViewModel — লাইফসাইকেল-কনশাস Weise UI-রিলেটেড ডেটা ম্যানেজ করুন
- WorkManager — আপনার Android ব্যাকগ্রাউন্ড জব ম্যানেজ করুন
Android Architecture Components-এর সাহায্যে আমরা MVVM আর্কিটেকচার প্যাটার্ন ইমপ্লিমেন্ট করব My Crypto Coins অ্যাপে এই ডায়াগ্রাম অনুসরণ করে:
এটি Google দ্বারা রিকমেন্ডেড আর্কিটেকচার। এটি দেখায় সব মডিউল একে অপরের সাথে কীভাবে ইন্টারঅ্যাক্ট করবে। এরপর আমরা শুধু নির্দিষ্ট Android Architecture Components কভার করব যা আমরা আমাদের প্রজেক্টে ব্যবহার করব।
আপনার সোর্স ফাইল অর্গানাইজ করা
ডেভেলপমেন্ট শুরু করার আগে আমাদের বিবেচনা করা উচিত আমাদের প্রজেক্টের সোর্স ফাইল কীভাবে অর্গানাইজ করব। আমরা এই প্রশ্নকে অনউত্তরিত ছাড়া রাখতে পারি না, কারণ পরে আমাদের একটি মেসি স্ট্রাকচার থাকবে যা বুঝতে ও মডিফাই করা কঠিন।
এটি করার একাধিক উপায় আছে। একটি হলো কম্পোনেন্ট ক্যাটেগরি অনুযায়ী অর্গানাইজ করা। উদাহরণস্বরূপ, সকল অ্যাক্টিভিটি তাদের নিজস্ব ফোল্ডারে যাবে, সকল অ্যাডাপ্টার তাদের ফোল্ডারে যাবে ইত্যাদি।
একটি অন্য উপায় হচ্ছে অ্যাপ ফিচার অনুযায়ী সব কিছু অর্গানাইজ করা। উদাহরণস্বরূপ, সার্চ এবং ক্রিপ্টো অ্যাড করা সব ক্রিপ্টো কারেন্সি লিস্ট ফিচার তার নিজস্ব addsearchlist ফোল্ডারে যাবে। মূল আইডিয়া হলো আপনাকে যেকোনো নির্দিষ্ট উপায়ে করতে হবে বরং র্যান্ডমলি সব কিছু রাখার বদলে। আমি উভয়ের একটু মিক্স ব্যবহার করি।
প্রজেক্টের ফোল্ডার স্ট্রাকচারের পাশাপাশি, আপনাকে প্রজেক্ট ফাইল নামকরণের কিছু নিয়ম বিবেচনা করা উচিত। উদাহরণস্বরূপ, Android ক্লাস নামকরণে ক্লাসের উদ্দেশ্য স্পষ্টভাবে নামে থাকা উচিত।
ViewModel
আমাদের অ্যাপ আর্কিটেকচার ডেভেলপমেন্টের শুরুতে, প্রথমে আমরা ViewModel তৈরি করব। View models হলো অবজেক্ট যা UI কম্পোনেন্টের জন্য ডেটা প্রদান করে এবং কনফিগারেশন চেঞ্জ servive করে।
আপনি একটি ViewModel ব্যবহার করতে পারেন একটি অ্যাক্টিভিটি বা ফ্র্যাগমেন্টের পুরো লাইফসাইকেল জুড়ে ডেটা রিটেইন করতে। অ্যাক্টিভিটি এবং ফ্র্যাগমেন্ট শর্ট-লিভড অবজেক্ট। ইউজার অ্যাপের সাথে ইন্টারঅ্যাক্ট করার সাথে সাথে সেগুলো خلق ও ধ্বংস হয়। ViewModel নেটওয়ার্ক কমিউনিকেশন, ডেটা ম্যানিপুলেশন এবং পারসিস্টেন্স সংক্রান্ত টাস্ক ম্যানেজ করার জন্যও আরও উপযুক্ত।
উদাহরণস্বরূপ এখন MainListFragment এর জন্য একটি ViewModel তৈরি করি UI ডেটা আলাদা করার জন্য।
class MainViewModel : ViewModel() { ... }
তারপর এক লাইন কোড দিয়ে ViewModel অপ্টেইন করুন।
class MainListFragment : Fragment() { ... private lateinit var viewModel: MainViewModel ... override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupList() // ViewModelProviders থেকে ViewModel অপ্টেইন করুন, এই ফ্র্যাগমেন্টকে LifecycleOwner হিসেবে ব্যবহার করে। viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) ... } ... }
বেসিক্যালি এটাই হলো, অভিনন্দন! ? চলুন এগিয়ে যাই।
LiveData
LiveData হলো একটি অবজার্ভেবল ডেটা হোল্ডার ক্লাস। এটি observer pattern ফলো করে। LiveData lifecycle-aware। এর মানে এটি শুধু অ্যাপ কম্পোনেন্ট (অ্যাক্টিভিটি, ফ্র্যাগমেন্ট, ইত্যাদি) observer কে আপডেট করে যা একটি এক্টিভ লাইফসাইকেল স্টেটে আছে।
LiveData ক্লাস ডেটার লেটেস্ট ভ্যালু রিটার্ন করে। ডেটা পরিবর্তন হলে আপডেটেড ভ্যালু রিটার্ন করে। LiveData ViewModel-র সাথে সেরা কাজ করে।
আমরা LiveData ViewModel-র সাথে এইভাবে ব্যবহার করব:
... class MainViewModel : ViewModel() { private val liveData = MutableLiveData<ArrayList<Cryptocurrency>>() val data: LiveData<ArrayList<Cryptocurrency>> get() = liveData init { val tempData = ArrayList<Cryptocurrency>() val btc:Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82) val eth:Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32) tempData.add(btc) tempData.add(eth) liveData.value = tempData } }
ViewModel-এ এক্সপোজড LiveData-কে অবজার্ভ করুন:
... class MainListFragment : Fragment() { private lateinit var recyclerView: RecyclerView private lateinit var recyclerAdapter: MainRecyclerViewAdapter private lateinit var viewModel: MainViewModel ... override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupList() // ViewModelProviders থেকে ViewModel অপ্টেইন করুন, এই ফ্র্যাগমেন্টকে LifecycleOwner হিসেবে ব্যবহার করে। viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) // ViewModel-এ এক্সপোজড LiveData-কে অবজার্ভ করুন viewModel.data.observe(this, Observer { data -> // LiveData দ্বারা এক্সপোজড ডেটা সেট করুন if (data != null) { recyclerAdapter.setData(data) } }) } ... }
রিপোজিটরি এই পয়েন্টে ইতিহাসে ব্রাউজ করুন।
Data Binding
Data Binding Library তৈরি হয়েছে XML লেআউটের সাথে কানেক্ট করার জন্য প্রয়োজনীয় বয়লারপ্লেট কোড সরাতে।
Kotlin প্রজেক্টে Data Binding ব্যবহার করতে, আপনাকে kapt কম্পাইলার প্লাগইন দিয়ে annotation processors-র সাপোর্ট অন করতে হবে। এছাড়া data binding ব্লক Android কনফিগারেশন gradle ফাইলে অ্যাড করতে হবে:
... apply plugin: 'kotlin-kapt' android { ... dataBinding { enabled = true } } ...
Data binding জেনারেটেড ক্লাস ব্যবহার করতে, আমাদের সকল ভিউ কোড <layout> ট্যাগের ভিতরে রাখতে হবে। Data binding-এর সবচেয়ে পাওয়ারফুল কনসেপ্ট হলো আমরা কিছু ডেটা ক্লাসকে XML লেআউট এবং আইটেম প্রপার্টিগুলোকে ফিল্ডের সাথে সরাসরি বাইন্ড করতে পারি।
<layout xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools"> <data> <variable name="cryptocurrency" type="com.baruckis.mycryptocoins.data.Cryptocurrency" /> </data> ... <android.support.v7.widget.AppCompatTextView android:id="@+id/item_name" style="@style/MainListItemPrimeText" android:layout_marginEnd="@dimen/main_cardview_list_item_text_between_margin" android:layout_marginStart="@dimen/main_cardview_list_item_inner_margin" android:text="@{cryptocurrency.name}" android:textAlignment="viewStart" app:layout_constraintBottom_toTopOf="@+id/item_amount_symbol" app:layout_constraintEnd_toStartOf="@+id/guideline1_percent" app:layout_constraintStart_toEndOf="@+id/item_image_icon" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="spread" tools:text="@string/sample_text_item_name" /> ... </layout>
Data binding সহ RecyclerView adapter এইরকম দেখাবে:
class MainRecyclerViewAdapter() : RecyclerView.Adapter<MainRecyclerViewAdapter.BindingViewHolder>() { private lateinit var dataList: ArrayList<Cryptocurrency> fun setData(newDataList: ArrayList<Cryptocurrency>) { dataList = newDataList } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = FragmentMainListItemBinding.inflate(inflater, parent, false) return BindingViewHolder(binding) } override fun onBindViewHolder(holder: BindingViewHolder, position: Int) = holder.bind(dataList[position]) override fun getItemCount(): Int = dataList.size ... inner class BindingViewHolder(var binding: FragmentMainListItemBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(cryptocurrency: Cryptocurrency) { binding.cryptocurrency = cryptocurrency binding.itemRanking.text = String.format("${cryptocurrency.rank}") ... binding.executePendingBindings() } } }
শেষে findViewById লিখতে হবে না ? রিপোজিটরি এই পয়েন্টে ইতিহাসে ব্রাউজ করুন।
Room
আমাদের অ্যাপে ব্যবহারকারী যে বিভিন্ন ক্রিপ্টোকারেন্সি ধারণ করে তার পারসিস্টেন্ট ডেটা সংরক্ষণ করতে হবে। এটি Android ডিভাইসের ভিতরে প্রাইভেটলি রাখা একটি লোকাল ডাটাবেসে সংরক্ষণ করা উচিত।
প্রাইভেট ডাটাবেসে স্ট্রাকচার্ড ডেটা সংরক্ষণের জন্য আমরা SQLite ডাটাবেস ব্যবহার করব। এটি প্রায়শই সেরা চয়েস।
আমাদের অ্যাপের জন্য SQLite ডাটাবেস তৈরি করার জন্য আমরা Room ব্যবহার করব। Room Android টিমের তৈরি একটি persistence library যা SQLite-এর ওপর একটি wrapper। এটি একটি abstraction layer যা SQLite-র সাথে ইন্টারঅ্যাক্ট করার জন্য প্রয়োজনীয় বয়লারপ্লেট কোডের অনেকাংশ সরিয়ে দেয়। এছাড়া এটি আপনার SQL কোয়েরির কম্পাইল-টাইম চেকিং যোগ করে।
এটিকে ভাবার সেরা উপায় হলো একটি ORM (Object Relational Mapper) টুল হিসেবে যা আপনার অবজেক্ট ইনস্ট্যান্স এবং ডাটাবেসের রোগুলোর মধ্যে ম্যাপ করার জন্য গ্লু কোড অটোমেটিক্যালি জেনারেট করে।
Room-এ মূলত ৩টি মেজর কম্পোনেন্ট আছে:
- Entity — এই কম্পোনেন্ট একটি ক্লাস রিপ্রেজেন্ট করে যা একটি ডাটাবেস রো ধারণ করে। প্রতিটি entity-এর জন্য, আইটেম ধারণ করার জন্য একটি ডাটাবেস টেবিল তৈরি হয়।
- DAO (Data Access Object) — মূল কম্পোনেন্ট যা ডাটাবেস এক্সেস করার মেথড ডিফাইন করার জন্য দায়বদ্ধ।
- Database — একটি কম্পোনেন্ট যা একটি হোল্ডার ক্লাস যা অ্যানোতেশন ব্যবহার করে entity-এর লিস্ট, DAO-এর লিস্ট, এবং ডাটাবেস ভার্সন ডিফাইন করে এবং অন্তর্নিহিত কানেকশনের জন্য মূল অ্যাক্সেস পয়েন্ট হিসেবে কাজ করে।
My Crypto Coins অ্যাপে Room সেটআপ করার জন্য এই সিম্পল ধাপগুলো ফলো করুন:
- একটি Entity তৈরি করুন।
@Entity data class Cryptocurrency(val name: String, val rank: Short, val amount: Double, @PrimaryKey val symbol: String, val price: Double, val amountFiat: Double, val pricePercentChange1h: Double, val pricePercentChange7d: Double, val pricePercentChange24h: Double, val amountFiatChange24h: Double)
Room-কে এর স্ট্রাকচার ডাটাবেসে সম্পর্কে জানাতে কিছু এক্সট্রা ইনফো যোগ করুন।
2. DAO তৈরি করুন।
@Dao interface MyCryptocurrencyDao { @Query("SELECT * FROM Cryptocurrency") fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>> @Insert fun insertDataToMyCryptocurrencyList(data: List<Cryptocurrency>) }
শুরুতে, আমরা একটি DAO তৈরি করব যা শুধু Entity-র সাথে তৈরি করা টেবিল থেকে রেকর্ড রিট্রিভ করতে এবং কিছু স্যাম্পল ডেটা ইনসার্ট করতে দেবে।
3. Database তৈরি ও সেটআপ করুন।
বলতে গেলে Database ইনস্ট্যান্স আদর্শভাবে প্রতি সেশনে একবার তৈরি হওয়া উচিত। এটিকে অর্জন করার একটি উপায় হচ্ছে Singleton pattern ব্যবহার করা।
@Database(entities = [Cryptocurrency::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun myCryptocurrencyDao(): MyCryptocurrencyDao // AppDatabase সিঙ্গলটন হওয়া উচিত একই সময়ে ডাটাবেসের একাধিক ইনস্ট্যান্স খোলা থেকে রোধ করার জন্য। companion object { // এনোতেটেড প্রোপার্টির JVM ব্যাকিং ফিল্ডকে volatile মার্ক করে, অর্থাৎ এই ফিল্ডে লেখা তাৎক্ষণিকভাবে অন্য থ্রেডগুলোর কাছে দৃশ্যমান হয়। @Volatile private var instance: AppDatabase? = null // Singleton instantiation-এর জন্য। fun getInstance(context: Context): AppDatabase { return instance ?: synchronized(this) { instance ?: buildDatabase(context).also { instance = it } } } // ডাটাবেস তৈরি ও প্রি-পপুলেট করে। private fun buildDatabase(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) // onCreate কল হওয়ার পর ডাটাবেস প্রি-পপুলেট করুন। .addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // IO থ্রেডে ডেটা ইনসার্ট করুন। ioThread { getInstance(context).myCryptocurrencyDao().insertDataToMyCryptocurrencyList(PREPOPULATE_DATA) } } }) .build() } // স্যাম্পল ডেটা। val btc: Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82) val eth: Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32) val PREPOPULATE_DATA = listOf(btc, eth) } }
private val IO_EXECUTOR = Executors.newSingleThreadExecutor() // ডেডিকেটেড ব্যাকগ্রাউন্ড থ্রেডে ব্লক রান করার ইউটিলিটি মেথড, IO/ডাটাবেস কাজের জন্য। fun ioThread(f : () -> Unit) { IO_EXECUTOR.execute(f) }
যে দেখতে পাচ্ছেন, ইনিশিয়াল রানে, ডাটাবেস কিছু স্যাম্পল ডেটা দিয়ে প্রি-পপুলেট হবে শুধু টেস্টিং উদ্দেশ্যে।
4. EXTRA step. Repository তৈরি করুন।
Repository Architecture Components লাইব্রেরির অংশ নয়। এটি কোড separation এবং আর্কিটেকচারের জন্য একটি suggested best practice।
এটি একটি সিঙ্গল সোর্স অফ ট্রুথ হিসেবে দাঁড়ায় সকল অ্যাপ ডেটার জন্য যদি আপনাকে একাধিক ডেটা সোর্স ম্যানেজ করতে হয়।
class MyCryptocurrencyRepository private constructor( private val myCryptocurrencyDao: MyCryptocurrencyDao ) { fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>> { return myCryptocurrencyDao.getMyCryptocurrencyLiveDataList() } companion object { // JVM ব্যাকিং ফিল্ডকে volatile মার্ক করে। @Volatile private var instance: MyCryptocurrencyRepository? = null // Singleton instantiation-এর জন্য। fun getInstance(myCryptocurrencyDao: MyCryptocurrencyDao) = instance ?: synchronized(this) { instance ?: MyCryptocurrencyRepository(myCryptocurrencyDao).also { instance = it } } } }
আমরা এই রিপোজিটরি আমাদের ViewModel-এ ব্যবহার করব।
class MainViewModel(myCryptocurrencyRepository: MyCryptocurrencyRepository) : ViewModel() { val liveData = myCryptocurrencyRepository.getMyCryptocurrencyLiveDataList() }
আমাদের Fragment কোডও বিকশিত হয়।
class MainListFragment : Fragment() { ... private lateinit var viewModel: MainViewModel ... override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupList() subscribeUi() } ... private fun subscribeUi() { val factory = InjectorUtils.provideMainViewModelFactory(requireContext()) // ViewModelProviders থেকে ViewModel অপ্টেইন করুন, এই ফ্র্যাগমেন্টকে LifecycleOwner হিসেবে ব্যবহার করে। viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java) // ডেটা পরিবর্তন হলে লিস্ট আপডেট করুন ViewModel-এ এক্সপোজড LiveData অবজার্ভ করে। viewModel.liveData.observe(this, Observer<List<Cryptocurrency>> { data -> if (data != null && data.isNotEmpty()) { emptyListView.visibility = View.GONE recyclerView.visibility = View.VISIBLE recyclerAdapter.setData(data) } else { recyclerView.visibility = View.GONE emptyListView.visibility = View.VISIBLE } }) } }
কারণ আমাদের ViewModel ক্লাসে এখন একটি কনস্ট্রাক্টর আছে যা খালি নয়, আমাদের একটি প্রোভাইডার ফ্যাক্টরি প্যাটার্ন ইমপ্লিমেন্ট করতে হবে। এটি ViewModelProviders.of() মেথডে দ্বিতীয় প্যারামিটার হিসেবে পাস করা হবে।
object InjectorUtils { fun provideMainViewModelFactory( context: Context ): MainViewModelFactory { val repository = getMyCryptocurrencyRepository(context) return MainViewModelFactory(repository) } private fun getMyCryptocurrencyRepository(context: Context): MyCryptocurrencyRepository { return MyCryptocurrencyRepository.getInstance( AppDatabase.getInstance(context).myCryptocurrencyDao()) } }
class MainViewModelFactory(private val repository: MyCryptocurrencyRepository) : ViewModelProvider.NewInstanceFactory() { override fun <T : ViewModel?> create(modelClass: Class<T>): T { return MainViewModel(repository) as T } }
রিপোজিটরি এই পয়েন্টে ইতিহাসে ব্রাউজ করুন।
Final thoughts
এই পার্টে আলোচনা করা ডিজাইন আর্কিটেকচারগুলোকে ইনফর্মড গাইডলাইন হিসেবে ব্যবহার করা উচিত কঠোর নিয়ম হিসেবে নয়। আমি প্রতিটি টপিকে খুব গভীরে যেতে চাইনি। Android Architecture Components-র সাথে আমরা কোডিং প্রসেস দেখেছি। মনে রাখবেন প্রতিটি কম্পোনেন্টে আলাদা করে অনেক কিছু শিখতে আছে এবং আমি পরামর্শ দিই আপনি সেটা করুন।
আমরা যা ইতিমধ্যে করতে পেরেছি সেটি সারমর্ম করি:
- My Crypto Coins অ্যাপে, প্রতিটি আলাদা স্ক্রিনের নিজস্ব ViewModel আছে। এটি যেকোনো কনফিগারেশন চেঞ্জ servive করে এবং ইউজারকে যেকোনো ডেটা লস থেকে রক্ষা করে।
- অ্যাপের ইউজার ইন্টারফেস রিয়েক্টিভ টাইপ। এর মানে ব্যাকএন্ডে ডেটা পরিবর্তন হলে তাৎক্ষণিক আপডেট হবে। এটি LiveData-র সাহায্যে করা হয়েছে।
- আমাদের প্রজেক্টে কম কোড আছে কারণ আমরা Data Binding ব্যবহার করে সরাসরি ভেরিয়েবল বাইন্ড করি।
- শেষে, আমাদের অ্যাপ ইউজার ডেটা লোকালি ডিভাইসে SQLite ডাটাবেস হিসেবে সংরক্ষণ করে। ডাটাবেস Room কম্পোনেন্ট দিয়ে সুবিধাজনকভাবে তৈরি করা হয়েছে। অ্যাপের কোড ফিচার অনুযায়ী স্ট্রাকচার্ড এবং পুরো প্রজেক্ট আর্কিটেকচার MVVM — Android টিম দ্বারা রিকমেন্ডেড প্যাটার্ন।
Repository
এখন, আপনি দেখছেন "Kriptofolio" (পূর্বে "My Crypto Coins") অ্যাপটি আসলে আকৃতি নিচ্ছে। এই পার্ট 3-এর লেটেস্ট রিপোজিটরি কমিটের সাথে, আপনি এটিকে প্রিপপুলেটেড ডাটাবেস ডেটা ইউজারের জন্য সুন্দরভাবে দেখাতে পাবেন টোটাল হোল্ডিংস পোর্টফোলিও ভ্যালু সঠিকভাবে ক্যালকুলেটেড।
View Source On GitHub
Ačiū! Thanks for reading! I originally published this post for my personal blog www.baruckis.com on August 22, 2018.