Dagger সম্পর্কিত প্রাথমিক বিষয়

Android অ্যাপে ম্যানুয়াল ডিপেন্ডেন্সি ইনজেকশন বা সার্ভিস লোকেটর ব্যবহার করলে, আপনার প্রোজেক্টের সাইজের উপর নির্ভর করে সমস্যা হতে পারে। আপনি ডিপেন্ডেন্সি ম্যানেজ করার জন্য Dagger ব্যবহার করে, আপনার প্রোজেক্টের জটিলতা সীমিত করতে পারেন, কারণ এটি স্কেল আপ করে।

Dagger অটোমেটিক এমন কোড জেনারেট করে যা আপনার হাতে লেখা কোডের মতো কাজ করে। কম্পাইল করার সময় কোড জেনারেট করা হয় বলে এটি ট্রেস করা যায় এবং Guice-এর মতো অন্যান্য রিফ্লেকশন-ভিত্তিক সমাধানের চেয়ে আরও বেশি পারফর্ম্যান্স দেয়।

Dagger ব্যবহারের সুবিধা

Dagger আপনাকে বিরক্তিকর এবং ত্রুটি-প্রবণ বয়লারপ্লেট কোড লেখা থেকে মুক্তি দেয়:

  • ম্যানুয়াল DI বিভাগে আপনি ম্যানুয়ালি যে AppContainer কোড (অ্যাপ্লিকেশন গ্রাফ) প্রয়োগ করেছেন তা জেনারেট করা হচ্ছে।

  • অ্যাপ্লিকেশন গ্রাফে উপলভ্য ক্লাসের জন্য ফ্যাক্টরি তৈরি করা হচ্ছে। এইভাবে ইন্টার্নাল ডিপেন্ডেন্সি পূরণ করা হয়।

  • স্কোপ ব্যবহার করে কোনও ডিপেন্ডেন্সি আবার ব্যবহার করা হবে নাকি নতুন ইনস্ট্যান্স তৈরি করা হবে তা স্থির করা।

  • Dagger সাবকম্পোনেন্ট ব্যবহার করে আগের বিভাগে লগ-ইন ফ্লোতে যেভাবে করেছেন, ঠিক সেইভাবে নির্দিষ্ট ফ্লোর জন্য কন্টেনার তৈরি করা। এটি আপনার অ্যাপের পারফর্ম্যান্স উন্নত করে, কারণ মেমরিতে থাকা অবজেক্টের আর প্রয়োজন না থাকলে তা রিলিজ করে দেয়।

যতক্ষণ আপনি কোনও ক্লাসের ডিপেন্ডেন্সি ঘোষণা করছেন এবং অ্যানোটেশন ব্যবহার করে কীভাবে সেগুলি পূরণ করতে হবে তা নির্দিষ্ট করছেন, ততক্ষণ Dagger বিল্ড টাইমে অটোমেটিক এই সব কিছু করে। Dagger এমন কোড জেনারেট করে যা আপনি ম্যানুয়ালি লিখলে যেমন হত, ঠিক তেমনই। অভ্যন্তরীণভাবে, Dagger অবজেক্টের একটি গ্রাফ তৈরি করে যা এটি রেফারেন্স করতে পারে একটি ক্লাসের একটি উদাহরণ প্রদান করার উপায় খুঁজে পেতে. গ্রাফের প্রতিটি ক্লাসের জন্য, Dagger একটি ফ্যাক্টরি-টাইপ ক্লাস তৈরি করে যা এটি সেই ধরনের ইনস্ট্যান্স পেতে ইন্টার্নালি ব্যবহার করে।

বিল্ড করার সময়, Dagger আপনার কোড চেক করে এবং:

  • ডিপেন্ডেন্সি গ্রাফ তৈরি ও যাচাই করে, এটি নিশ্চিত করে যে:

    • প্রতিটি অবজেক্টের ডিপেন্ডেন্সি পূরণ করা যেতে পারে, তাই কোনও রানটাইম ব্যতিক্রম নেই।
    • কোনও ডিপেনডেন্সি সাইকেল নেই, তাই কোনও ইনফিনিট লুপ নেই।
  • রানটাইমে আসল অবজেক্ট ও তাদের ডিপেন্ডেন্সি তৈরি করার জন্য যেসব ক্লাস ব্যবহার করা হয় সেগুলি জেনারেট করে।

Dagger-এ একটি সাধারণ ব্যবহারের উদাহরণ: একটি ফ্যাক্টরি তৈরি করা

Dagger-এর সাথে আপনি কীভাবে কাজ করতে পারেন তা দেখাতে, নিচের ডায়াগ্রামে দেখানো UserRepository ক্লাসের জন্য একটি সহজ ফ্যাক্টরি তৈরি করা যাক:

UserRepository-কে এইভাবে ব্যাখ্যা করুন:

Kotlin

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

জাভা

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }

    ...
}

UserRepository কনস্ট্রাক্টরে একটি @Inject অ্যানোটেশন যোগ করুন যাতে Dagger জানতে পারে কীভাবে একটি UserRepository তৈরি করতে হয়:

Kotlin

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

জাভা

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    // @Inject lets Dagger know how to create instances of this object
    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

উপরের কোড স্নিপেটে, আপনি Dagger-কে বলছেন:

  1. @Inject অ্যানোটেটেড কনস্ট্রাক্টর সহ কীভাবে UserRepository ইনস্ট্যান্স তৈরি করতে হয়।

  2. এটি কোন কোন আইটেমের উপর নির্ভরশীল: UserLocalDataSource এবং UserRemoteDataSource।

এখন Dagger জানে কীভাবে UserRepository-এর ইনস্ট্যান্স তৈরি করতে হয়, কিন্তু এটি জানে না কীভাবে এর ডিপেন্ডেন্সি তৈরি করতে হয়। আপনি যদি অন্য ক্লাসগুলিও অ্যানোটেট করেন, Dagger জানে কীভাবে সেগুলি তৈরি করতে হয়:

Kotlin

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

জাভা

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}

public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

Dagger কম্পোনেন্ট

Dagger আপনার প্রোজেক্টে ডিপেন্ডেন্সির গ্রাফ তৈরি করতে পারে যা এটি প্রয়োজন হলে সেই ডিপেন্ডেন্সিগুলি কোথায় পাওয়া যাবে তা খুঁজে বের করতে ব্যবহার করতে পারে। Dagger-এর মাধ্যমে এটি করতে, আপনাকে একটি ইন্টারফেস তৈরি করতে হবে এবং @Component-এর মাধ্যমে এটি অ্যানোটেট করতে হবে। ম্যানুয়াল ডিপেন্ডেন্সি ইনজেকশনের মাধ্যমে আপনি যেভাবে কন্টেনার তৈরি করতেন, Dagger-ও ঠিক সেইভাবেই কন্টেনার তৈরি করে।

@Component ইন্টারফেসের মধ্যে, আপনি এমন ফাংশন সংজ্ঞায়িত করতে পারেন যা আপনার প্রয়োজনীয় ক্লাসের ইনস্ট্যান্স (যেমন UserRepository) রিটার্ন করে। @Component Dagger-কে এমন একটি কন্টেইনার তৈরি করতে বলে যাতে এটি যে ধরনের এক্সপোজ করে তা পূরণ করার জন্য প্রয়োজনীয় সমস্ত ডিপেন্ডেন্সি থাকে। একে Dagger কম্পোনেন্ট বলা হয়; এতে একটি গ্রাফ থাকে যা এমন অবজেক্ট নিয়ে গঠিত যেগুলি কীভাবে প্রদান করতে হয় তা Dagger জানে এবং তাদের সংশ্লিষ্ট ডিপেন্ডেন্সিও জানে।

Kotlin

// @Component makes Dagger create a graph of dependencies
@Component
interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be provided from the container
    fun repository(): UserRepository
}

জাভা

// @Component makes Dagger create a graph of dependencies
@Component
public interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be consumed from the graph
    UserRepository userRepository();
}

আপনি প্রোজেক্ট তৈরি করার সময়, Dagger আপনার জন্য ApplicationGraph ইন্টারফেসের একটি ইমপ্লিমেন্টেশন তৈরি করে: DaggerApplicationGraph। এর অ্যানোটেশন প্রসেসরের সাহায্যে, Dagger একটি ডিপেন্ডেন্সি গ্রাফ তৈরি করে যা তিনটি ক্লাসের (UserRepository, UserLocalDatasource এবং UserRemoteDataSource) মধ্যে সম্পর্ক নিয়ে গঠিত এবং এতে শুধুমাত্র একটি এন্ট্রি পয়েন্ট থাকে: UserRepository ইনস্ট্যান্স পাওয়া। আপনি এটি নিম্নলিখিত উপায়ে ব্যবহার করতে পারবেন:

Kotlin

// Create an instance of the application graph
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
// Grab an instance of UserRepository from the application graph
val userRepository: UserRepository = applicationGraph.repository()

জাভা

// Create an instance of the application graph
ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

// Grab an instance of UserRepository from the application graph
UserRepository userRepository = applicationGraph.userRepository();

Dagger প্রতিবার অনুরোধ করা হলে UserRepository-এর একটি নতুন ইনস্ট্যান্স তৈরি করে।

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)

জাভা

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository != userRepository2)

কখনও কখনও, আপনাকে একটি কন্টেনারে ডিপেন্ডেন্সির একটি অনন্য ইনস্ট্যান্স থাকতে হবে। আপনি বিভিন্ন কারণে এটি চাইতে পারেন:

  1. আপনি চান যে অন্যান্য ধরন, যার এই ধরনের উপর নির্ভরতা আছে, একই ইনস্ট্যান্স শেয়ার করুক, যেমন লগইন ফ্লোতে একাধিক ViewModel অবজেক্ট একই LoginUserData ব্যবহার করে।

  2. কোনও অবজেক্ট তৈরি করতে অনেক খরচ হয় এবং আপনি চান না যে প্রতিবার এটি ডিপেন্ডেন্সি হিসেবে ঘোষিত হলে নতুন ইনস্ট্যান্স তৈরি হোক (যেমন, JSON পার্সার)।

উদাহরণস্বরূপ, আপনি হয়ত গ্রাফে UserRepository-এর একটি অনন্য ইনস্ট্যান্স উপলভ্য করতে চান যাতে প্রতিবার আপনি UserRepository-এর জন্য জিজ্ঞাসা করলে, আপনি সবসময় একই ইনস্ট্যান্স পান। আপনার উদাহরণের ক্ষেত্রে এটি উপযোগী কারণ আরও জটিল অ্যাপ্লিকেশন গ্রাফ সহ বাস্তব জীবনের অ্যাপ্লিকেশনে, আপনার কাছে UserRepository-এর উপর নির্ভর করে একাধিক ViewModel অবজেক্ট থাকতে পারে এবং আপনি প্রতিবার UserRepository প্রদান করার প্রয়োজন হলে UserLocalDataSource এবং UserRemoteDataSource-এর নতুন ইনস্ট্যান্স তৈরি করতে চাইবেন না।

ম্যানুয়াল ডিপেন্ডেন্সি ইনজেকশনে, আপনি ViewModel ক্লাসের কনস্ট্রাক্টরে UserRepository-এর একই ইনস্ট্যান্স পাস করে এটি করেন; কিন্তু Dagger-এ, আপনি ম্যানুয়ালি সেই কোড লিখছেন না বলে, আপনি যে একই ইনস্ট্যান্স ব্যবহার করতে চান তা Dagger-কে জানাতে হবে। এটি স্কোপ অ্যানোটেশন-এর মাধ্যমে করা যেতে পারে।

Dagger-এর সাহায্যে স্কোপিং করা

আপনি স্কোপ অ্যানোটেশন ব্যবহার করে কোনও অবজেক্টের লাইফটাইমকে তার কম্পোনেন্টের লাইফটাইমে সীমিত করতে পারেন। এর অর্থ হল, যখনই সেই ধরনের ডিপেন্ডেন্সি প্রদান করার প্রয়োজন হয়, তখনই একই ইনস্ট্যান্স ব্যবহার করা হয়।

আপনি যখন ApplicationGraph-এ রিপোজিটরির জন্য জিজ্ঞাসা করেন তখন UserRepository-এর একটি অনন্য ইনস্ট্যান্স পেতে, @Component ইন্টারফেস এবং UserRepository-এর জন্য একই স্কোপ অ্যানোটেশন ব্যবহার করুন। আপনি @Singleton অ্যানোটেশন ব্যবহার করতে পারেন যা আগে থেকেই Dagger-এর ব্যবহার করা javax.inject প্যাকেজের সাথে আসে:

Kotlin

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are bound to the life of the graph and so
// the same instance of that type is provided every time the type is requested.
@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

জাভা

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@Singleton
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

এছাড়াও, আপনি কাস্টম স্কোপ অ্যানোটেশন তৈরি করে ব্যবহার করতে পারেন। আপনি নিম্নলিখিত উপায়ে স্কোপ অ্যানোটেশন তৈরি করতে পারবেন:

Kotlin

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

জাভা

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

তারপর, আপনি এটি আগের মতো ব্যবহার করতে পারবেন:

Kotlin

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }

জাভা

@MyCustomScope
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

@MyCustomScope
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

উভয় ক্ষেত্রেই, অবজেক্টটি @Component ইন্টারফেস অ্যানোটেট করার জন্য ব্যবহৃত একই স্কোপের সাথে প্রদান করা হয়। তাই, প্রতিবার আপনি applicationGraph.repository() কল করলে, আপনি UserRepository-এর একই ইনস্ট্যান্স পাবেন।

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)

জাভা

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository == userRepository2)

সিদ্ধান্ত

আরও জটিল পরিস্থিতিতে Dagger ব্যবহার করার আগে, এর সুবিধা এবং এটি কীভাবে কাজ করে তার প্রাথমিক বিষয়গুলি সম্পর্কে সচেতন হওয়া গুরুত্বপূর্ণ।

পরবর্তী পৃষ্ঠায়, আপনি Android অ্যাপ্লিকেশনে কীভাবে Dagger যোগ করতে হয় তা শিখবেন।