Android অ্যাপে Dagger ব্যবহার করা

Dagger-এর প্রাথমিক বিষয় পৃষ্ঠায় ব্যাখ্যা করা হয়েছে যে কীভাবে Dagger আপনার অ্যাপে ডিপেন্ডেন্সি ইনজেকশন অটোমেট করতে সাহায্য করতে পারে। Dagger-এর সাহায্যে, আপনাকে ক্লান্তিকর এবং ত্রুটি-প্রবণ বয়লারপ্লেট কোড লিখতে হবে না।

পেশাদার পদ্ধতির সারসংক্ষেপ

  • সম্ভব হলে ড্যাগার গ্রাফে ধরন যোগ করতে @Inject-এর সাথে কন্সট্রাক্টর ইনজেকশন ব্যবহার করুন। যখন এটি হয় না:
    • ইন্টারফেসে কোন ইমপ্লিমেন্টেশন থাকা উচিত তা Dagger-কে জানাতে @Binds ব্যবহার করুন।
    • আপনার প্রোজেক্টের মালিকানাধীন নয় এমন ক্লাস কীভাবে প্রদান করতে হবে তা Dagger-কে জানাতে @Provides ব্যবহার করুন।
  • একটি কম্পোনেন্টে আপনাকে একবারই মডিউল ঘোষণা করতে হবে।
  • যেখানে অ্যানোটেশন ব্যবহার করা হয় সেখানে লাইফটাইমের উপর নির্ভর করে স্কোপ অ্যানোটেশনের নাম দিন। উদাহরণ হিসেবে @ApplicationScope, @LoggedUserScope এবং @ActivityScope-এর কথা বলা যায়।

ডিপেন্ডেন্সি যোগ করা হচ্ছে

আপনার প্রোজেক্টে Dagger ব্যবহার করতে, আপনার build.gradle ফাইলে আপনার অ্যাপ্লিকেশনে এই ডিপেন্ডেন্সিগুলি যোগ করুন। আপনি Dagger-এর লেটেস্ট ভার্সন এই GitHub প্রোজেক্টে খুঁজে পাবেন।

Kotlin

plugins {
  id 'kotlin-kapt'
}

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}

জাভা

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

Dagger in Android

ছবি ১-এ দেখানো ডিপেন্ডেন্সি গ্রাফ সহ একটি Android অ্যাপের উদাহরণ বিবেচনা করুন।

LoginActivity, LoginViewModel-এর উপর নির্ভর করে, যা UserRepository-এর উপর নির্ভর করে,
  যা UserLocalDataSource এবং UserRemoteDataSource-এর উপর নির্ভর করে, যা আবার
  Retrofit-এর উপর নির্ভর করে।

ছবি ১. উদাহরণ কোডের ডিপেন্ডেন্সি গ্রাফ

Android-এ, আপনি সাধারণত একটি ড্যাগার গ্রাফ তৈরি করেন যা আপনার অ্যাপ্লিকেশন ক্লাসে থাকে কারণ আপনি চান যে অ্যাপটি চলাকালীন গ্রাফের একটি ইনস্ট্যান্স মেমরিতে থাকুক। এইভাবে, গ্রাফটি অ্যাপ লাইফসাইকেলের সাথে অ্যাটাচ করা হয়। কিছু কিছু ক্ষেত্রে, আপনি গ্রাফে অ্যাপ্লিকেশনের প্রসঙ্গও উপলভ্য করতে চাইতে পারেন। তার জন্য, আপনাকে গ্রাফটি Application ক্লাসেও রাখতে হবে। এই পদ্ধতির একটি সুবিধা হল যে গ্রাফটি অন্যান্য Android ফ্রেমওয়ার্ক ক্লাসের জন্য উপলভ্য। এছাড়াও, এটি আপনাকে টেস্টে কাস্টম Application ক্লাস ব্যবহার করার অনুমতি দিয়ে টেস্টিং সহজ করে তোলে।

যে ইন্টারফেস গ্রাফ তৈরি করে সেটি @Component দিয়ে অ্যানোটেট করা থাকে, তাই আপনি এটিকে ApplicationComponent বা ApplicationGraph বলতে পারেন। সাধারণত আপনি আপনার কাস্টম Application ক্লাসে সেই কম্পোনেন্টের একটি ইনস্ট্যান্স রাখেন এবং প্রতিবার অ্যাপ্লিকেশন গ্রাফের প্রয়োজন হলে এটিকে কল করেন, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

Kotlin

// Definition of the Application graph
@Component
interface ApplicationComponent { ... }

// appComponent lives in the Application class to share its lifecycle
class MyApplication: Application() {
    // Reference to the application graph that is used across the whole app
    val appComponent = DaggerApplicationComponent.create()
}

জাভা

// Definition of the Application graph
@Component
public interface ApplicationComponent {
}

// appComponent lives in the Application class to share its lifecycle
public class MyApplication extends Application {

    // Reference to the application graph that is used across the whole app
    ApplicationComponent appComponent = DaggerApplicationComponent.create();
}

অ্যাক্টিভিটি ও ফ্র্যাগমেন্টের মতো নির্দিষ্ট কিছু Android ফ্রেমওয়ার্ক ক্লাস সিস্টেমের মাধ্যমে ইনস্ট্যান্টিয়েট করা হয় বলে, Dagger আপনার জন্য সেগুলি তৈরি করতে পারে না। অ্যাক্টিভিটির জন্য নির্দিষ্টভাবে, যেকোনও ইনিশিয়ালাইজেশন কোড onCreate() পদ্ধতিতে যেতে হবে। এর অর্থ হল, আপনি আগের উদাহরণের মতো ক্লাসের (কন্সট্রাক্টর ইনজেকশন) কন্সট্রাক্টরে @Inject অ্যানোটেশন ব্যবহার করতে পারবেন না। পরিবর্তে, আপনাকে ফিল্ড ইনজেকশন ব্যবহার করতে হবে।

onCreate() পদ্ধতিতে কোনও অ্যাক্টিভিটির জন্য প্রয়োজনীয় ডিপেন্ডেন্সি তৈরি করার পরিবর্তে, আপনি চান যে Dagger আপনার জন্য সেই ডিপেন্ডেন্সিগুলি পূরণ করুক। ফিল্ড ইনজেকশনের জন্য, আপনি পরিবর্তে সেই ফিল্ডগুলিতে @Inject অ্যানোটেশন প্রয়োগ করেন যা আপনি Dagger গ্রাফ থেকে পেতে চান।

Kotlin

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel
}

জাভা

public class LoginActivity extends Activity {

    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject
    LoginViewModel loginViewModel;
}

সহজভাবে বলতে গেলে, LoginViewModel কোনও Android আর্কিটেকচার কম্পোনেন্ট ViewModel নয়; এটি একটি সাধারণ ক্লাস যা ViewModel হিসেবে কাজ করে। এইসব ক্লাস কীভাবে ইনজেক্ট করতে হয় সেই সম্পর্কে আরও তথ্যের জন্য, অফিসিয়াল Android Blueprints Dagger প্রয়োগ-এর dev-dagger ব্রাঞ্চে কোড চেক করুন।

Dagger-এর ক্ষেত্রে একটি বিষয় বিবেচনা করতে হবে যে ইনজেক্ট করা ফিল্ড ব্যক্তিগত হতে পারে না। পূর্ববর্তী কোডের মতো এগুলির অন্তত প্যাকেজ-প্রাইভেট দৃশ্যমানতা থাকতে হবে।

অ্যাক্টিভিটি ইনজেক্ট করা হচ্ছে

Dagger-কে জানতে হবে যে LoginActivity-কে গ্রাফ অ্যাক্সেস করতে হবে যাতে প্রয়োজনীয় ViewModel প্রদান করা যায়। Dagger-এর প্রাথমিক বিষয় পৃষ্ঠায়, আপনি গ্রাফ থেকে অবজেক্ট পেতে @Component ইন্টারফেস ব্যবহার করেছেন গ্রাফ থেকে আপনি যা পেতে চান তার রিটার্ন টাইপ সহ ফাংশন প্রকাশ করার মাধ্যমে। এই ক্ষেত্রে, আপনাকে Dagger-কে এমন একটি অবজেক্ট (এই ক্ষেত্রে LoginActivity ) সম্পর্কে জানাতে হবে যার জন্য ডিপেন্ডেন্সি ইনজেক্ট করতে হবে। তার জন্য, আপনি এমন একটি ফাংশন প্রকাশ করেন যা ইনজেকশনের অনুরোধ করা অবজেক্টকে প্যারামিটার হিসেবে গ্রহণ করে।

Kotlin

@Component
interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is requesting.
    fun inject(activity: LoginActivity)
}

জাভা

@Component
public interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is injecting.
    void inject(LoginActivity loginActivity);
}

এই ফাংশন Dagger-কে জানায় যে LoginActivity গ্রাফ অ্যাক্সেস করতে চায় এবং ইনজেকশনের অনুরোধ করে। Dagger-কে সেইসব ডিপেন্ডেন্সি পূরণ করতে হবে যা LoginActivity-এর প্রয়োজন (LoginViewModel-এর নিজস্ব ডিপেন্ডেন্সি সহ)। আপনার একাধিক ক্লাস থাকলে যেগুলি ইনজেকশনের অনুরোধ করে, আপনাকে বিশেষভাবে কম্পোনেন্টে তাদের সঠিক ধরন সহ সবকটি ঘোষণা করতে হবে। যেমন, আপনার কাছে ইনজেকশনের অনুরোধ করা LoginActivity এবং RegistrationActivity থাকলে, দুটি ক্ষেত্রেই প্রযোজ্য এমন একটি জেনেরিক পদ্ধতির পরিবর্তে আপনার কাছে দুটি inject() পদ্ধতি থাকবে। একটি জেনেরিক inject() পদ্ধতি Dagger-কে বলে না যে কী প্রদান করতে হবে। ইন্টারফেসের ফাংশনগুলির যেকোনও নাম থাকতে পারে, তবে প্যারামিটার হিসেবে ইনজেক্ট করার জন্য অবজেক্ট পেলে তাদের inject() বলা Dagger-এর একটি কনভেনশন।

অ্যাক্টিভিটিতে কোনও অবজেক্ট ইনজেক্ট করতে, আপনি appComponent ব্যবহার করবেন যা আপনার Application ক্লাসে সংজ্ঞায়িত করা আছে এবং inject() মেথড কল করবেন, ইনজেকশনের অনুরোধ করে এমন অ্যাক্টিভিটির একটি ইনস্ট্যান্স পাস করবেন।

অ্যাক্টিভিটি ব্যবহার করার সময়, ফ্র্যাগমেন্ট রিস্টোরেশন সংক্রান্ত সমস্যা এড়াতে, super.onCreate() কল করার আগে অ্যাক্টিভিটির onCreate() পদ্ধতিতে ড্যাগার ইনজেক্ট করুন। super.onCreate()-এ রিস্টোর করার সময়, কোনও অ্যাক্টিভিটি ফ্র্যাগমেন্ট অ্যাটাচ করে যা অ্যাক্টিভিটি বাইন্ডিং অ্যাক্সেস করতে চাইতে পারে।

ফ্র্যাগমেন্ট ব্যবহার করার সময়, ফ্র্যাগমেন্টের onAttach() মেথডে Dagger ইনজেক্ট করুন। এই ক্ষেত্রে, super.onAttach()-এ কল করার আগে বা পরে এটি করা যেতে পারে।

Kotlin

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        (applicationContext as MyApplication).appComponent.inject(this)
        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

// @Inject tells Dagger how to create instances of LoginViewModel
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

জাভা

public class LoginActivity extends Activity {

    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        ((MyApplication) getApplicationContext()).appComponent.inject(this);
        // Now loginViewModel is available

        super.onCreate(savedInstanceState);
    }
}

public class LoginViewModel {

    private final UserRepository userRepository;

    // @Inject tells Dagger how to create instances of LoginViewModel
    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

আসুন Dagger-কে বলি কীভাবে গ্রাফ তৈরি করার জন্য বাকি ডিপেন্ডেন্সি প্রদান করতে হবে:

Kotlin

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

class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor(
    private val loginService: LoginRetrofitService
) { ... }

জাভা

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

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

public class UserLocalDataSource {

    @Inject
    public UserLocalDataSource() {}
}

public class UserRemoteDataSource {

    private final LoginRetrofitService loginRetrofitService;

    @Inject
    public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) {
        this.loginRetrofitService = loginRetrofitService;
    }
}

Dagger মডিউল

এই উদাহরণের জন্য, আপনি Retrofit নেটওয়ার্কিং লাইব্রেরি ব্যবহার করছেন। UserRemoteDataSource, LoginRetrofitService-এর উপর নির্ভরশীল। তবে, LoginRetrofitService-এর ইনস্ট্যান্স তৈরি করার পদ্ধতি আপনি এতদিন যেভাবে করে এসেছেন তার থেকে আলাদা। এটি কোনও ক্লাস ইনস্ট্যান্টিয়েশন নয়; এটি হল Retrofit.Builder() কল করার এবং লগ-ইন পরিষেবা কনফিগার করার জন্য বিভিন্ন প্যারামিটার পাস করার ফলাফল।

@Inject অ্যানোটেশন ছাড়াও, Dagger-কে কীভাবে কোনও ক্লাসের ইনস্ট্যান্স প্রদান করতে হবে তা বলার আরেকটি উপায় আছে: Dagger মডিউলের মধ্যে থাকা তথ্য। Dagger মডিউল হল এমন একটি ক্লাস যা @Module-এর সাথে অ্যানোটেট করা থাকে। সেখানে, আপনি @Provides অ্যানোটেশন সহ ডিপেন্ডেন্সি নির্ধারণ করতে পারবেন।

Kotlin

// @Module informs Dagger that this class is a Dagger Module
@Module
class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService::class.java)
    }
}

জাভা

// @Module informs Dagger that this class is a Dagger Module
@Module
public class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    public LoginRetrofitService provideLoginRetrofitService() {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return new Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService.class);
    }
}

মডিউল হল অবজেক্ট কীভাবে প্রদান করা যায় সেই সম্পর্কে তথ্য শব্দার্থগতভাবে এনক্যাপসুলেট করার একটি উপায়। আপনি দেখতে পাচ্ছেন যে নেটওয়ার্কিং সম্পর্কিত অবজেক্ট প্রদান করার লজিক গ্রুপ করতে আপনি NetworkModule ক্লাসকে কল করেছেন। অ্যাপ্লিকেশনটি প্রসারিত হলে, আপনি এখানে কীভাবে OkHttpClient প্রদান করবেন বা কীভাবে Gson বা Moshi কনফিগার করবেন তাও যোগ করতে পারেন।

@Provides পদ্ধতির ডিপেন্ডেন্সি হল সেই পদ্ধতির প্যারামিটার। আগের পদ্ধতির জন্য, LoginRetrofitService কোনও ডিপেন্ডেন্সি ছাড়াই প্রদান করা যেতে পারে কারণ পদ্ধতিতে কোনও প্যারামিটার নেই। আপনি যদি প্যারামিটার হিসেবে OkHttpClient ঘোষণা করে থাকেন, তাহলে LoginRetrofitService-এর ডিপেন্ডেন্সি পূরণ করার জন্য Dagger-কে গ্রাফ থেকে OkHttpClient ইনস্ট্যান্স প্রদান করতে হবে। যেমন:

Kotlin

@Module
class NetworkModule {
    // Hypothetical dependency on LoginRetrofitService
    @Provides
    fun provideLoginRetrofitService(
        okHttpClient: OkHttpClient
    ): LoginRetrofitService { ... }
}

জাভা

@Module
public class NetworkModule {

    @Provides
    public LoginRetrofitService provideLoginRetrofitService(OkHttpClient okHttpClient) {
        ...
    }
}

Dagger গ্রাফ যাতে এই মডিউল সম্পর্কে জানতে পারে, তার জন্য আপনাকে এটি @Component ইন্টারফেসে এইভাবে যোগ করতে হবে:

Kotlin

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    ...
}

জাভা

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    ...
}

Dagger গ্রাফে ধরন যোগ করার জন্য সাজেস্ট করা পদ্ধতি হল কন্সট্রাক্টর ইনজেকশন (যেমন, ক্লাসের কন্সট্রাক্টরে @Inject অ্যানোটেশন সহ) ব্যবহার করা। কখনও কখনও, এটি সম্ভব হয় না এবং আপনাকে Dagger মডিউল ব্যবহার করতে হয়। একটি উদাহরণ হল, আপনি যখন চান যে Dagger কোনও অবজেক্টের ইনস্ট্যান্স কীভাবে তৈরি করতে হবে তা নির্ধারণ করার জন্য কোনও কম্পিউটেশনের ফলাফল ব্যবহার করুক। যখনই সেই ধরনের কোনও ইনস্ট্যান্স প্রদান করতে হয়, Dagger @Provides মেথডের মধ্যে কোড রান করে।

উদাহরণে দেখানো ড্যাগার গ্রাফটি এখন এইরকম দেখতে:

LoginActivity ডিপেন্ডেন্সি গ্রাফের ডায়াগ্রাম

ছবি ২. Dagger-এর মাধ্যমে LoginActivity ইনজেক্ট করা গ্রাফের উপস্থাপনা

গ্রাফের এন্ট্রি পয়েন্ট হল LoginActivity। কারণ LoginActivity ইনজেক্ট করে LoginViewModel, Dagger এমন একটি গ্রাফ তৈরি করে যা জানে কীভাবে LoginViewModel-এর ইনস্ট্যান্স প্রদান করতে হয় এবং রিকার্সিভভাবে, এর ডিপেন্ডেন্সি। Dagger জানে কীভাবে এটি করতে হয় কারণ ক্লাসের কনস্ট্রাক্টরে @Inject অ্যানোটেশন থাকে।

Dagger-এর তৈরি করা ApplicationComponent-এর মধ্যে, ফ্যাক্টরি-টাইপ মেথড আছে যা কীভাবে প্রদান করতে হয় তা জানা সব ক্লাসের ইনস্ট্যান্স পেতে সাহায্য করে। এই উদাহরণে, LoginRetrofitService-এর ইনস্ট্যান্স পেতে, Dagger ApplicationComponent-এ অন্তর্ভুক্ত NetworkModule-কে ডেলিগেট করে।

Dagger স্কোপ

Dagger-এর প্রাথমিক বিষয় পৃষ্ঠায় স্কোপের উল্লেখ করা হয়েছে, যাতে কম্পোনেন্টে কোনও ধরনের অনন্য ইনস্ট্যান্স পাওয়া যায়। কম্পোনেন্টের লাইফসাইকেলে কোনও টাইপ স্কোপ করা বলতে এটিই বোঝানো হয়।

আপনি হয়ত অ্যাপের অন্যান্য ফিচারে UserRepository ব্যবহার করতে চান এবং প্রতিবার প্রয়োজন হলে নতুন অবজেক্ট তৈরি করতে চান না, তাই আপনি এটিকে পুরো অ্যাপের জন্য একটি অনন্য ইনস্ট্যান্স হিসেবে মনোনীত করতে পারেন। এটি LoginRetrofitService-এর ক্ষেত্রেও একই: এটি তৈরি করতে অনেক খরচ হতে পারে এবং আপনিও চান যে সেই অবজেক্টের একটি অনন্য ইনস্ট্যান্স আবার ব্যবহার করা হোক। UserRemoteDataSource-এর ইনস্ট্যান্স তৈরি করতে বেশি খরচ হয় না, তাই কম্পোনেন্টের লাইফসাইকেলে এটি স্কোপ করার প্রয়োজন নেই।

@Singleton হল একমাত্র স্কোপ অ্যানোটেশন যা javax.inject প্যাকেজের সাথে আসে। আপনি এটি ব্যবহার করে ApplicationComponent এবং পুরো অ্যাপ্লিকেশন জুড়ে যেসব অবজেক্ট আবার ব্যবহার করতে চান সেগুলি অ্যানোটেট করতে পারবেন।

Kotlin

@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Module
class NetworkModule {
    // Way to scope types inside a Dagger Module
    @Singleton
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService { ... }
}

জাভা

@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    void inject(LoginActivity loginActivity);
}

@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;
    }
}

@Module
public class NetworkModule {

    @Singleton
    @Provides
    public LoginRetrofitService provideLoginRetrofitService() { ... }
}

অবজেক্টে স্কোপ প্রয়োগ করার সময় মেমরি লিক যাতে না হয় সেই ব্যাপারে সতর্ক থাকুন। যতক্ষণ স্কোপ করা কম্পোনেন্ট মেমরিতে থাকে, ততক্ষণ তৈরি করা অবজেক্টও মেমরিতে থাকে। কারণ অ্যাপ চালু করার সময় ApplicationComponent তৈরি করা হয় (Application ক্লাসে), অ্যাপ বন্ধ হয়ে গেলে এটিও বন্ধ হয়ে যায়। তাই, অ্যাপ্লিকেশনটি ধ্বংস না হওয়া পর্যন্ত UserRepository-এর অনন্য ইনস্ট্যান্স সর্বদা মেমরিতে থাকে।

Dagger সাবকম্পোনেন্ট

আপনার লগ-ইন ফ্লোতে (একটি LoginActivity দ্বারা ম্যানেজ করা) একাধিক ফ্র্যাগমেন্ট থাকলে, আপনাকে সব ফ্র্যাগমেন্টে LoginViewModel-এর একই ইনস্ট্যান্স আবার ব্যবহার করতে হবে। নিম্নলিখিত কারণে ইনস্ট্যান্স আবার ব্যবহার করার জন্য @Singleton, LoginViewModel অ্যানোটেট করতে পারছে না:

  1. ফ্লো শেষ হওয়ার পরেও LoginViewModel-এর ইনস্ট্যান্স মেমরিতে থেকে যাবে।

  2. আপনি প্রতিটি লগ-ইন ফ্লোয়ের জন্য LoginViewModel-এর আলাদা ইনস্ট্যান্স চান। যেমন, ব্যবহারকারী লগ-আউট করলে, আপনি LoginViewModel-এর অন্য ইনস্ট্যান্স চান, ব্যবহারকারী প্রথমবার লগ-ইন করার সময় যে ইনস্ট্যান্স ছিল সেটি নয়।

LoginActivity-এর লাইফসাইকেলে LoginViewModel-এর স্কোপ নির্ধারণ করতে, আপনাকে লগ-ইন ফ্লোয়ের জন্য একটি নতুন কম্পোনেন্ট (একটি নতুন সাবগ্রাফ) এবং একটি নতুন স্কোপ তৈরি করতে হবে।

লগ-ইন ফ্লোর জন্য নির্দিষ্ট একটি গ্রাফ তৈরি করা যাক।

Kotlin

@Component
interface LoginComponent {}

জাভা

@Component
public interface LoginComponent {
}

এখন, LoginActivity-এর LoginComponent থেকে ইনজেকশন পাওয়া উচিত কারণ এটির লগ-ইন-নির্দিষ্ট কনফিগারেশন আছে। এটি ApplicationComponent ক্লাস থেকে LoginActivity ইনজেক্ট করার দায়িত্ব সরিয়ে দেয়।

Kotlin

@Component
interface LoginComponent {
    fun inject(activity: LoginActivity)
}

জাভা

@Component
public interface LoginComponent {
    void inject(LoginActivity loginActivity);
}

LoginComponent-কে অবশ্যই ApplicationComponent থেকে অবজেক্ট অ্যাক্সেস করতে পারতে হবে কারণ LoginViewModel, UserRepository-এর উপর নির্ভর করে। Dagger-কে আপনি যদি জানাতে চান যে অন্য কম্পোনেন্টের অংশ ব্যবহার করার জন্য আপনি নতুন কম্পোনেন্ট চান, তাহলে Dagger সাবকম্পোনেন্ট ব্যবহার করতে হবে। নতুন কম্পোনেন্টটি অবশ্যই শেয়ার করা রিসোর্স সহ কম্পোনেন্টের সাবকম্পোনেন্ট হতে হবে।

সাবকম্পোনেন্ট হল এমন কম্পোনেন্ট যা পেরেন্ট কম্পোনেন্টের অবজেক্ট গ্রাফ ইনহেরিট ও এক্সটেন্ড করে। তাই, মূল কম্পোনেন্টে দেওয়া সব অবজেক্ট সাবকম্পোনেন্টেও দেওয়া হয়। এইভাবে, সাবকম্পোনেন্টের কোনও অবজেক্ট মূল কম্পোনেন্টের মাধ্যমে প্রদান করা অবজেক্টের উপর নির্ভর করতে পারে।

সাবকম্পোনেন্টের ইনস্ট্যান্স তৈরি করতে, আপনাকে পেরেন্ট কম্পোনেন্টের ইনস্ট্যান্স তৈরি করতে হবে। তাই, পেরেন্ট কম্পোনেন্ট থেকে সাবকম্পোনেন্টে প্রদান করা অবজেক্ট এখনও পেরেন্ট কম্পোনেন্টের স্কোপে থাকে।

উদাহরণে, আপনাকে অবশ্যই LoginComponent-কে ApplicationComponent-এর সাবকম্পোনেন্ট হিসেবে সংজ্ঞায়িত করতে হবে। এটি করতে, LoginComponent-কে @Subcomponent দিয়ে অ্যানোটেট করুন:

Kotlin

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
interface LoginComponent {

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    fun inject(loginActivity: LoginActivity)
}

জাভা

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
public interface LoginComponent {

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    void inject(LoginActivity loginActivity);
}

এছাড়াও, আপনাকে LoginComponent-এর মধ্যে একটি সাবকম্পোনেন্ট ফ্যাক্টরি সংজ্ঞায়িত করতে হবে যাতে ApplicationComponent জানে কীভাবে LoginComponent-এর ইনস্ট্যান্স তৈরি করতে হয়।

Kotlin

@Subcomponent
interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
}

জাভা

@Subcomponent
public interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        LoginComponent create();
    }

    void inject(LoginActivity loginActivity);
}

Dagger-কে জানাতে যে LoginComponent হল ApplicationComponent-এর সাবকম্পোনেন্ট, আপনাকে এটি এইভাবে উল্লেখ করতে হবে:

  1. নতুন Dagger মডিউল (যেমন, SubcomponentsModule) তৈরি করা হচ্ছে যা সাবকম্পোনেন্টের ক্লাসকে অ্যানোটেশনের subcomponents অ্যাট্রিবিউটে পাস করে।

    Kotlin

    // The "subcomponents" attribute in the @Module annotation tells Dagger what
    // Subcomponents are children of the Component this module is included in.
    @Module(subcomponents = LoginComponent::class)
    class SubcomponentsModule {}

    জাভা

    // The "subcomponents" attribute in the @Module annotation tells Dagger what
    // Subcomponents are children of the Component this module is included in.
    @Module(subcomponents = LoginComponent.class)
    public class SubcomponentsModule {
    }
  2. ApplicationComponent-এ নতুন মডিউল (যেমন, SubcomponentsModule) যোগ করা:

    Kotlin

    // Including SubcomponentsModule, tell ApplicationComponent that
    // LoginComponent is its subcomponent.
    @Singleton
    @Component(modules = [NetworkModule::class, SubcomponentsModule::class])
    interface ApplicationComponent {
    }

    জাভা

    // Including SubcomponentsModule, tell ApplicationComponent that
    // LoginComponent is its subcomponent.
    @Singleton
    @Component(modules = {NetworkModule.class, SubcomponentsModule.class})
    public interface ApplicationComponent {
    }

    মনে রাখবেন যে ApplicationComponent-কে আর LoginActivity ইনজেক্ট করতে হবে না কারণ সেই দায়িত্ব এখন LoginComponent-এর, তাই আপনি ApplicationComponent থেকে inject() পদ্ধতি সরিয়ে দিতে পারেন।

    ApplicationComponent-এর গ্রাহকদের জানতে হবে কীভাবে LoginComponent-এর ইনস্ট্যান্স তৈরি করতে হয়। প্যারেন্ট কম্পোনেন্টকে তার ইন্টারফেসে একটি পদ্ধতি যোগ করতে হবে যাতে গ্রাহকরা প্যারেন্ট কম্পোনেন্টের ইনস্ট্যান্স থেকে সাবকম্পোনেন্টের ইনস্ট্যান্স তৈরি করতে পারেন:

  3. ইন্টারফেসে LoginComponent-এর ইনস্ট্যান্স তৈরি করে এমন ফ্যাক্টরি এক্সপোজ করুন:

    Kotlin

    @Singleton
    @Component(modules = [NetworkModule::class, SubcomponentsModule::class])
    interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    fun loginComponent(): LoginComponent.Factory
    }

    জাভা

    @Singleton
    @Component(modules = { NetworkModule.class, SubcomponentsModule.class} )
    public interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    LoginComponent.Factory loginComponent();
    }

সাবকম্পোনেন্টে স্কোপ অ্যাসাইন করা হচ্ছে

আপনি প্রোজেক্ট তৈরি করলে, ApplicationComponent এবং LoginComponent, দুটিরই ইনস্ট্যান্স তৈরি করতে পারবেন। ApplicationComponent অ্যাপ্লিকেশনের লাইফসাইকেলের সাথে অ্যাটাচ করা থাকে কারণ আপনি গ্রাফের একই ইনস্ট্যান্স ব্যবহার করতে চান যতক্ষণ অ্যাপ্লিকেশনটি মেমরিতে থাকে।

LoginComponent-এর লাইফসাইকেল কী? আপনার LoginComponent প্রয়োজন হওয়ার অন্যতম কারণ হল, লগ-ইন সম্পর্কিত ফ্র্যাগমেন্টের মধ্যে আপনাকে LoginViewModel-এর একই ইনস্ট্যান্স শেয়ার করতে হবে। কিন্তু, আপনি নতুন লগ-ইন ফ্লো থাকলে, LoginViewModel-এর বিভিন্ন ইনস্ট্যান্স চান। LoginActivity হল LoginComponent-এর জন্য সঠিক লাইফটাইম: প্রতিটি নতুন অ্যাক্টিভিটির জন্য, আপনার প্রয়োজন LoginComponent-এর একটি নতুন ইনস্ট্যান্স এবং ফ্র্যাগমেন্ট যা LoginComponent-এর সেই ইনস্ট্যান্স ব্যবহার করতে পারে।

যেহেতু LoginComponent, LoginActivity লাইফসাইকেলের সাথে অ্যাটাচ করা আছে, তাই আপনাকে অ্যাক্টিভিটিতে কম্পোনেন্টের রেফারেন্স রাখতে হবে, ঠিক যেভাবে আপনি Application ক্লাসে applicationComponent-এর রেফারেন্স রেখেছেন। এইভাবে, ফ্র্যাগমেন্ট এটি অ্যাক্সেস করতে পারবে।

Kotlin

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent
    ...
}

জাভা

public class LoginActivity extends Activity {

    // Reference to the Login graph
    LoginComponent loginComponent;

    ...
}

লক্ষ্য করুন যে loginComponent ভেরিয়েবেলটি @Inject-এর সাথে অ্যানোটেট করা নেই কারণ আপনি আশা করছেন না যে Dagger এই ভেরিয়েবেলটি প্রদান করবে।

আপনি LoginComponent-এর রেফারেন্স পেতে ApplicationComponent ব্যবহার করতে পারেন এবং তারপরে নিম্নলিখিত উপায়ে LoginActivity ইনজেক্ট করতে পারেন:

Kotlin

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Creation of the login graph using the application graph
        loginComponent = (applicationContext as MyDaggerApplication)
                            .appComponent.loginComponent().create()

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this)

        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

জাভা

public class LoginActivity extends Activity {

    // Reference to the Login graph
    LoginComponent loginComponent;

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Creation of the login graph using the application graph
        loginComponent = ((MyApplication) getApplicationContext())
                                .appComponent.loginComponent().create();

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this);

        // Now loginViewModel is available

        super.onCreate(savedInstanceState);
    }
}

অ্যাক্টিভিটির onCreate() পদ্ধতিতে LoginComponent তৈরি করা হয় এবং অ্যাক্টিভিটি নষ্ট হয়ে গেলে এটিও নষ্ট হয়ে যাবে।

LoginComponent-কে প্রতিবার অনুরোধ করা হলে, LoginViewModel-এর একই ইনস্ট্যান্স প্রদান করতে হবে। আপনি কাস্টম অ্যানোটেশন স্কোপ তৈরি করে এবং এটির মাধ্যমে LoginComponent ও LoginViewModel উভয়কেই অ্যানোটেট করে এটি নিশ্চিত করতে পারবেন। মনে রাখবেন যে আপনি @Singleton অ্যানোটেশন ব্যবহার করতে পারবেন না কারণ এটি ইতিমধ্যেই প্যারেন্ট কম্পোনেন্ট ব্যবহার করেছে এবং এটি অবজেক্টটিকে একটি অ্যাপ্লিকেশন সিঙ্গলটন (পুরো অ্যাপের জন্য অনন্য ইনস্ট্যান্স) করে তুলবে। আপনাকে অন্য একটি অ্যানোটেশন স্কোপ তৈরি করতে হবে।

এই ক্ষেত্রে, আপনি এই স্কোপ @LoginScope কল করতে পারতেন, তবে এটি একটি ভাল অভ্যাস নয়। স্কোপ অ্যানোটেশনের নাম এমন হওয়া উচিত নয় যা থেকে তার উদ্দেশ্য স্পষ্ট বোঝা যায়। পরিবর্তে, এটির লাইফটাইমের উপর নির্ভর করে এটির নাম দেওয়া উচিত কারণ অ্যানোটেশনগুলি RegistrationComponent এবং SettingsComponent-এর মতো সিবলিং কম্পোনেন্ট দ্বারা পুনরায় ব্যবহার করা যেতে পারে। তাই @LoginScope-এর পরিবর্তে @ActivityScope বলা উচিত।

Kotlin

// Definition of a custom scope called ActivityScope
@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

// Classes annotated with @ActivityScope are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@ActivityScope
@Subcomponent
interface LoginComponent { ... }

// A unique instance of LoginViewModel is provided in Components
// annotated with @ActivityScope
@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

জাভা

// Definition of a custom scope called ActivityScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}

// Classes annotated with @ActivityScope are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@ActivityScope
@Subcomponent
public interface LoginComponent { ... }

// A unique instance of LoginViewModel is provided in Components
// annotated with @ActivityScope
@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

এখন, আপনার কাছে যদি দুটি ফ্র্যাগমেন্ট থাকে যেগুলির LoginViewModel প্রয়োজন, তাহলে দুটিকেই একই ইনস্ট্যান্স প্রদান করা হয়। যেমন, আপনার কাছে যদি একটি LoginUsernameFragment এবং একটি LoginPasswordFragment থাকে, তাহলে সেগুলি LoginComponent-এর মাধ্যমে ইনজেক্ট করতে হবে:

Kotlin

@ActivityScope
@Subcomponent
interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment
    // request injection from LoginComponent. The graph needs to satisfy
    // all the dependencies of the fields those classes are injecting
    fun inject(loginActivity: LoginActivity)
    fun inject(usernameFragment: LoginUsernameFragment)
    fun inject(passwordFragment: LoginPasswordFragment)
}

জাভা

@ActivityScope
@Subcomponent
public interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        LoginComponent create();
    }

    // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment
    // request injection from LoginComponent. The graph needs to satisfy
    // all the dependencies of the fields those classes are injecting
    void inject(LoginActivity loginActivity);
    void inject(LoginUsernameFragment loginUsernameFragment);
    void inject(LoginPasswordFragment loginPasswordFragment);
}

কম্পোনেন্টগুলি কম্পোনেন্টের ইনস্ট্যান্স অ্যাক্সেস করে যা LoginActivity অবজেক্টে থাকে। LoginUserNameFragment-এর জন্য উদাহরণ কোড নিম্নলিখিত কোড স্নিপেটে দেখা যায়:

Kotlin

class LoginUsernameFragment: Fragment() {

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        // Obtaining the login graph from LoginActivity and instantiate
        // the @Inject fields with objects from the graph
        (activity as LoginActivity).loginComponent.inject(this)

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

জাভা

public class LoginUsernameFragment extends Fragment {

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        // Obtaining the login graph from LoginActivity and instantiate
        // the @Inject fields with objects from the graph
        ((LoginActivity) getActivity()).loginComponent.inject(this);

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

এবং LoginPasswordFragment-এর জন্য একই:

Kotlin

class LoginPasswordFragment: Fragment() {

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity as LoginActivity).loginComponent.inject(this)

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

জাভা

public class LoginPasswordFragment extends Fragment {

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        ((LoginActivity) getActivity()).loginComponent.inject(this);

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

নতুন সাবকম্পোনেন্ট সহ Dagger গ্রাফ কেমন দেখতে হয় তা ছবি ৩-এ দেখানো হয়েছে। সাদা ডট (UserRepository, LoginRetrofitService, এবং LoginViewModel) সহ ক্লাসগুলি হল সেগুলি যেগুলির নিজ নিজ কম্পোনেন্টে অনন্য ইনস্ট্যান্স স্কোপ করা আছে।

শেষ সাবকম্পোনেন্ট যোগ করার পরে অ্যাপ্লিকেশন গ্রাফ

ছবি ৩. Android অ্যাপের উদাহরণের জন্য আপনার তৈরি করা গ্রাফের উপস্থাপনা

গ্রাফের বিভিন্ন অংশ বিশ্লেষণ করা যাক:

  1. NetworkModule (এবং সেই কারণে LoginRetrofitService) ApplicationComponent-এ অন্তর্ভুক্ত করা হয়েছে কারণ আপনি কম্পোনেন্টে এটি নির্দিষ্ট করেছেন।

  2. UserRepository ApplicationComponent-এ থেকে যায় কারণ এটি ApplicationComponent-এর স্কোপে রয়েছে। প্রোজেক্টের পরিধি বাড়লে, আপনি বিভিন্ন ফিচার জুড়ে একই ইনস্ট্যান্স শেয়ার করতে চান (যেমন, রেজিস্ট্রেশন)।

    UserRepository যেহেতু ApplicationComponent-এর অংশ, তাই UserRepository-এর ইনস্ট্যান্স প্রদান করতে হলে এর ডিপেন্ডেন্সি (যেমন UserLocalDataSource এবং UserRemoteDataSource) এই কম্পোনেন্টেও থাকতে হবে।

  3. LoginViewModel-কে LoginComponent-এ অন্তর্ভুক্ত করা হয়েছে কারণ এটি শুধুমাত্র LoginComponent-এর দ্বারা ইনজেক্ট করা ক্লাসের জন্য প্রয়োজন। LoginViewModel, ApplicationComponent-এ অন্তর্ভুক্ত নয় কারণ ApplicationComponent-এর কোনও ডিপেন্ডেন্সির জন্য LoginViewModel-এর প্রয়োজন নেই।

    একইভাবে, আপনি যদি UserRepository-কে ApplicationComponent-এ স্কোপ না করতেন, তাহলে Dagger অটোমেটিক UserRepository এবং এর ডিপেন্ডেন্সিগুলিকে LoginComponent-এর অংশ হিসেবে অন্তর্ভুক্ত করত কারণ বর্তমানে এটিই একমাত্র জায়গা যেখানে UserRepository ব্যবহার করা হয়।

অবজেক্টকে অন্য লাইফসাইকেলে স্কোপ করার পাশাপাশি, সাবকম্পোনেন্ট তৈরি করা হল আপনার অ্যাপ্লিকেশনের বিভিন্ন অংশকে একে অপরের থেকে এনক্যাপসুলেট করার একটি ভালো অভ্যাস।

আপনার অ্যাপের ফ্লো অনুযায়ী বিভিন্ন Dagger সাবগ্রাফ তৈরি করার জন্য অ্যাপ স্ট্রাকচার করলে, মেমরি ও স্টার্ট-আপ টাইমের দিক থেকে আরও বেশি পারফর্ম্যান্স ও স্কেলেবল অ্যাপ্লিকেশন তৈরি করতে সাহায্য করে।

Dagger গ্রাফ তৈরি করার পেশাদার পদ্ধতি

আপনার অ্যাপ্লিকেশনের জন্য Dagger গ্রাফ তৈরি করার সময়:

  • আপনি যখন কোনও কম্পোনেন্ট তৈরি করেন, তখন আপনার বিবেচনা করা উচিত যে কোন এলিমেন্টটি সেই কম্পোনেন্টের লাইফটাইমের জন্য দায়ী। এই ক্ষেত্রে, Application ক্লাস ApplicationComponent-এর দায়িত্বে এবং LoginActivity ক্লাস LoginComponent-এর দায়িত্বে আছে।

  • শুধুমাত্র অর্থপূর্ণ হলেই স্কোপিং ব্যবহার করুন। স্কোপিংয়ের অতিরিক্ত ব্যবহার আপনার অ্যাপের রানটাইম পারফর্ম্যান্সের উপর নেতিবাচক প্রভাব ফেলতে পারে: কম্পোনেন্ট যতক্ষণ মেমরিতে থাকে ততক্ষণ অবজেক্ট মেমরিতে থাকে এবং স্কোপড অবজেক্ট পাওয়া আরও ব্যয়বহুল। Dagger অবজেক্ট প্রদান করলে, এটি ফ্যাক্টরি-টাইপ প্রোভাইডারের পরিবর্তে DoubleCheck লকিং ব্যবহার করে।

Dagger ব্যবহার করে এমন একটি প্রোজেক্ট পরীক্ষা করা

Dagger-এর মতো ডিপেন্ডেন্সি ইনজেকশন ফ্রেমওয়ার্ক ব্যবহার করার একটি সুবিধা হল যে এটি আপনার কোড পরীক্ষা করা সহজ করে তোলে।

ইউনিট টেস্ট

ইউনিট টেস্টের জন্য আপনাকে Dagger ব্যবহার করতে হবে না। কন্সট্রাক্টর ইনজেকশন ব্যবহার করে এমন ক্লাস পরীক্ষা করার সময়, সেই ক্লাস ইনস্ট্যান্টিয়েট করার জন্য আপনাকে Dagger ব্যবহার করতে হবে না। আপনি সরাসরি এর কনস্ট্রাক্টরকে কল করতে পারেন, ফেক বা মক ডিপেন্ডেন্সি পাস করে সরাসরি ঠিক যেমন আপনি করতেন যদি সেগুলি অ্যানোটেট করা না থাকত।

যেমন, LoginViewModel পরীক্ষা করার সময়:

Kotlin

@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

class LoginViewModelTest {

    @Test
    fun `Happy path`() {
        // You don't need Dagger to create an instance of LoginViewModel
        // You can pass a fake or mock UserRepository
        val viewModel = LoginViewModel(fakeUserRepository)
        assertEquals(...)
    }
}

জাভা

@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

public class LoginViewModelTest {

    @Test
    public void happyPath() {
        // You don't need Dagger to create an instance of LoginViewModel
        // You can pass a fake or mock UserRepository
        LoginViewModel viewModel = new LoginViewModel(fakeUserRepository);
        assertEquals(...);
    }
}

এন্ড-টু-এন্ড টেস্ট

ইন্টিগ্রেশন টেস্ট-এর জন্য, টেস্টিংয়ের উদ্দেশ্যে একটি TestApplicationComponent তৈরি করা একটি ভালো অভ্যাস। প্রোডাকশন ও টেস্টিংয়ের জন্য আলাদা কম্পোনেন্ট কনফিগারেশন ব্যবহার করা হয়।

এর জন্য আপনার অ্যাপ্লিকেশনে আরও বেশি আপ-ফ্রন্ট মডিউলের ডিজাইন প্রয়োজন। টেস্টিং কম্পোনেন্ট প্রোডাকশন কম্পোনেন্টকে এক্সটেন্ড করে এবং বিভিন্ন ধরনের মডিউল ইনস্টল করে।

Kotlin

// TestApplicationComponent extends from ApplicationComponent to have them both
// with the same interface methods. You need to include the modules of the
// component here as well, and you can replace the ones you want to override.
// This sample uses FakeNetworkModule instead of NetworkModule
@Singleton
@Component(modules = [FakeNetworkModule::class, SubcomponentsModule::class])
interface TestApplicationComponent : ApplicationComponent {
}

জাভা

// TestApplicationComponent extends from ApplicationComponent to have them both
// with the same interface methods. You need to include the modules of the
// Component here as well, and you can replace the ones you want to override.
// This sample uses FakeNetworkModule instead of NetworkModule
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}

FakeNetworkModule-এ আসল NetworkModule-এর নকল ইমপ্লিমেন্টেশন আছে। সেখানে আপনি যা কিছু পরিবর্তন করতে চান তার নকল উদাহরণ বা মক প্রদান করতে পারেন।

Kotlin

// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService
// that you can use in your tests.
@Module
class FakeNetworkModule {
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        return FakeLoginService()
    }
}

জাভা

// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService
// that you can use in your tests.
@Module
public class FakeNetworkModule {

    @Provides
    public LoginRetrofitService provideLoginRetrofitService() {
        return new FakeLoginService();
    }
}

আপনার ইন্টিগ্রেশন বা এন্ড-টু-এন্ড টেস্টে, আপনি এমন একটি TestApplication ব্যবহার করবেন যা ApplicationComponent-এর পরিবর্তে TestApplicationComponent তৈরি করে।

Kotlin

// Your test application needs an instance of the test graph
class MyTestApplication: MyApplication() {
    override val appComponent = DaggerTestApplicationComponent.create()
}

জাভা

// Your test application needs an instance of the test graph
public class MyTestApplication extends MyApplication {
    ApplicationComponent appComponent = DaggerTestApplicationComponent.create();
}

তারপর, এই টেস্ট অ্যাপ্লিকেশনটি একটি কাস্টম TestRunner-এ ব্যবহার করা হয় যা আপনি ইনস্ট্রুমেন্টেশন টেস্ট চালানোর জন্য ব্যবহার করবেন। এই বিষয়ে আরও জানতে, আপনার Android অ্যাপ কোডল্যাবে Dagger ব্যবহার করা নিবন্ধটি দেখুন।

Dagger মডিউল নিয়ে কাজ করা

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

কোনও কম্পোনেন্ট বা অন্য মডিউলে একবার মডিউল যোগ করা হলে, সেটি আগে থেকেই Dagger গ্রাফে থাকে; Dagger সেই কম্পোনেন্টে ওই অবজেক্টগুলি প্রদান করতে পারে। কোনও মডিউল যোগ করার আগে, সেই মডিউলটি ইতিমধ্যেই Dagger গ্রাফের অংশ কিনা তা চেক করুন। এটি ইতিমধ্যেই কম্পোনেন্টে যোগ করা হয়েছে কিনা তা চেক করে বা প্রোজেক্ট কম্পাইল করে এবং Dagger সেই মডিউলের জন্য প্রয়োজনীয় ডিপেন্ডেন্সি খুঁজে পেতে পারে কিনা তা দেখে এটি করতে পারেন।

ভালো প্র্যাকটিস অনুযায়ী, কোনও কম্পোনেন্টে মডিউল একবারই ঘোষণা করা উচিত (নির্দিষ্ট উন্নত Dagger ব্যবহারের ক্ষেত্র ছাড়া)।

ধরা যাক, আপনি এইভাবে গ্রাফ কনফিগার করেছেন। ApplicationComponent-এর মধ্যে Module1 ও Module2 এবং Module1-এর মধ্যে ModuleX অন্তর্ভুক্ত।

Kotlin

@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module
class Module2 { ... }

জাভা

@Component(modules = {Module1.class, Module2.class})
public interface ApplicationComponent { ... }

@Module(includes = {ModuleX.class})
public class Module1 { ... }

@Module
public class Module2 { ... }

এখন Module2, ModuleX-এর দেওয়া ক্লাসের উপর নির্ভর করে কিনা। Module2-এ ModuleX অন্তর্ভুক্ত করা হল একটি খারাপ অভ্যাস কারণ গ্রাফে ModuleX দুবার অন্তর্ভুক্ত করা হয়েছে যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখা গেছে:

Kotlin

// Bad practice: ModuleX is declared multiple times in this Dagger graph
@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module(includes = [ModuleX::class])
class Module2 { ... }

জাভা

// Bad practice: ModuleX is declared multiple times in this Dagger graph.
@Component(modules = {Module1.class, Module2.class})
public interface ApplicationComponent { ... }

@Module(includes = ModuleX.class)
public class Module1 { ... }

@Module(includes = ModuleX.class)
public class Module2 { ... }

পরিবর্তে, আপনাকে নিম্নলিখিতগুলির মধ্যে একটি করতে হবে:

  1. মডিউলগুলি রিফ্যাক্টর করুন এবং সাধারণ মডিউলটি কম্পোনেন্টে এক্সট্র্যাক্ট করুন।
  2. দুটি মডিউলই শেয়ার করে এমন অবজেক্টের সাহায্যে নতুন মডিউল তৈরি করুন এবং কম্পোনেন্টে এটি এক্সট্র্যাক্ট করুন।

এইভাবে রিফ্যাক্টর না করলে, অনেক মডিউল একে অপরের সাথে যুক্ত হয়ে যায়, যার ফলে স্পষ্ট অর্গানাইজেশন থাকে না এবং প্রতিটি ডিপেন্ডেন্সি কোথা থেকে আসছে তা বোঝা আরও কঠিন হয়ে যায়।

ভাল অভ্যাস (বিকল্প ১): Dagger গ্রাফে ModuleX একবার ঘোষণা করা হয়েছে।

Kotlin

@Component(modules = [Module1::class, Module2::class, ModuleX::class])
interface ApplicationComponent { ... }

@Module
class Module1 { ... }

@Module
class Module2 { ... }

জাভা

@Component(modules = {Module1.class, Module2.class, ModuleX.class})
public interface ApplicationComponent { ... }

@Module
public class Module1 { ... }

@Module
public class Module2 { ... }

পেশাদার পদ্ধতি (বিকল্প ২): ModuleX-এ Module1 ও Module2-এর সাধারণ ডিপেন্ডেন্সি ModuleXCommon নামের নতুন মডিউলে এক্সট্র্যাক্ট করা হয় যা কম্পোনেন্টে অন্তর্ভুক্ত থাকে। তারপর, ModuleXWithModule1Dependencies এবং ModuleXWithModule2Dependencies নামের অন্য দুটি মডিউল তৈরি করা হয়, যেগুলি প্রতিটি মডিউলের জন্য নির্দিষ্ট ডিপেন্ডেন্সি সহ তৈরি করা হয়। Dagger গ্রাফে সব মডিউল একবার ঘোষণা করা হয়।

Kotlin

@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class])
interface ApplicationComponent { ... }

@Module
class ModuleXCommon { ... }

@Module
class ModuleXWithModule1SpecificDependencies { ... }

@Module
class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = [ModuleXWithModule1SpecificDependencies::class])
class Module1 { ... }

@Module(includes = [ModuleXWithModule2SpecificDependencies::class])
class Module2 { ... }

জাভা

@Component(modules = {Module1.class, Module2.class, ModuleXCommon.class})
public interface ApplicationComponent { ... }

@Module
public class ModuleXCommon { ... }

@Module
public class ModuleXWithModule1SpecificDependencies { ... }

@Module
public class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = ModuleXWithModule1SpecificDependencies.class)
public class Module1 { ... }

@Module(includes = ModuleXWithModule2SpecificDependencies.class)
public class Module2 { ... }

অ্যাসিস্টেড ইনজেকশন

অ্যাসিস্টেড ইনজেকশন হল একটি DI প্যাটার্ন যা এমন একটি অবজেক্ট তৈরি করতে ব্যবহৃত হয় যেখানে কিছু প্যারামিটার DI ফ্রেমওয়ার্ক দ্বারা প্রদান করা হতে পারে এবং অন্যগুলি অবশ্যই ব্যবহারকারীর দ্বারা তৈরির সময় পাস করতে হবে।

Android-এ, এই প্যাটার্নটি সাধারণত বিবরণ স্ক্রিনে দেখা যায় যেখানে দেখানোর জন্য এলিমেন্টের আইডি শুধুমাত্র রানটাইমে জানা যায়, কম্পাইল টাইমে নয় যখন Dagger, DI গ্রাফ তৈরি করে। Dagger-এর সাথে অ্যাসিস্টেড ইনজেকশন সম্পর্কে আরও জানতে, Dagger ডকুমেন্টেশন দেখুন।

সিদ্ধান্ত

আপনি আগে থেকে না করে থাকলে, পেশাদার পদ্ধতি বিভাগ পর্যালোচনা করুন। Android অ্যাপে Dagger কীভাবে ব্যবহার করতে হয় তা জানতে, Android অ্যাপে Dagger ব্যবহার করা কোডল্যাব দেখুন।