মাল্টি-মডিউল অ্যাপে Dagger ব্যবহার করা

একাধিক Gradle মডিউল সহ প্রোজেক্টকে মাল্টি-মডিউল প্রোজেক্ট বলা হয়। মাল্টি-মডিউল প্রোজেক্টে, যা কোনও ফিচার মডিউল ছাড়াই একটি APK হিসেবে শিপ করা হয়, সেখানে একটি app মডিউল থাকা সাধারণ ব্যাপার যা আপনার প্রোজেক্টের বেশিরভাগ মডিউলের উপর নির্ভর করতে পারে এবং একটি base বা core মডিউল যার উপর বাকি মডিউলগুলি সাধারণত নির্ভর করে। app মডিউলে সাধারণত আপনার Application ক্লাস থাকে, যেখানে base মডিউলে আপনার প্রোজেক্টের সব মডিউল জুড়ে শেয়ার করা সব সাধারণ ক্লাস থাকে।

app মডিউল হল আপনার অ্যাপ্লিকেশন কম্পোনেন্ট (যেমন, নিচের ছবিতে ApplicationComponent) ঘোষণা করার জন্য একটি ভালো জায়গা যা আপনার অ্যাপের সিঙ্গলটনের পাশাপাশি অন্যান্য কম্পোনেন্টের প্রয়োজন হতে পারে এমন অবজেক্ট প্রদান করতে পারে। উদাহরণস্বরূপ, OkHttpClient, JSON পার্সার, আপনার ডেটাবেসের অ্যাক্সেসর বা SharedPreferences অবজেক্টের মতো ক্লাস যা core মডিউলে সংজ্ঞায়িত করা যেতে পারে, app মডিউলে সংজ্ঞায়িত ApplicationComponent দ্বারা প্রদান করা হবে।

app মডিউলে, আপনার কাছে কম আয়ুষ্কালের অন্যান্য কম্পোনেন্টও থাকতে পারে। উদাহরণস্বরূপ, লগ-ইন করার পরে ব্যবহারকারী-নির্দিষ্ট কনফিগারেশন সহ UserComponent (যেমন UserSession)।

আপনার প্রোজেক্টের বিভিন্ন মডিউলে, আপনি অন্তত একটি সাবকম্পোনেন্ট নির্ধারণ করতে পারেন যার লজিক সেই মডিউলের জন্য নির্দিষ্ট, যেমন চিত্র ১-এ দেখানো হয়েছে।

ছবি ১. মাল্টি-মডিউল প্রোজেক্টে ড্যাগার গ্রাফের উদাহরণ

যেমন, login মডিউলে, আপনার কাছে কাস্টম @ModuleScope অ্যানোটেশন সহ স্কোপ করা LoginComponent থাকতে পারে যা সেই ফিচারের জন্য সাধারণ অবজেক্ট প্রদান করতে পারে, যেমন LoginRepository। সেই মডিউলের মধ্যে, আপনার কাছে অন্যান্য কম্পোনেন্টও থাকতে পারে যা একটি ভিন্ন কাস্টম স্কোপ সহ একটি LoginComponent-এর উপর নির্ভর করে, উদাহরণস্বরূপ একটি LoginActivityComponent-এর জন্য @FeatureScope বা একটি TermsAndConditionsComponent যেখানে আপনি আরও ফিচার-নির্দিষ্ট লজিক স্কোপ করতে পারেন যেমন ViewModel অবজেক্ট।

Registration-এর মতো অন্যান্য মডিউলের জন্য, আপনার একই ধরনের সেট-আপ থাকবে।

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

পেশাদার পদ্ধতি হিসেবে, আপনি সাধারণত নিম্নলিখিত ক্ষেত্রে মডিউলে কম্পোনেন্ট তৈরি করবেন:

  • LoginActivityComponent-এর মতো আপনাকে ফিল্ড ইনজেকশন করতে হবে।

  • LoginComponent-এর মতো আপনাকে অবজেক্টের স্কোপ করতে হবে।

এইসব কেসের কোনওটিই প্রযোজ্য না হলে এবং সেই মডিউল থেকে কীভাবে অবজেক্ট প্রদান করতে হবে তা Dagger-কে জানাতে হলে, @Provides বা @Binds পদ্ধতি সহ Dagger মডিউল তৈরি করে এক্সপোজ করুন, যদি সেইসব ক্লাসের জন্য কনস্ট্রাকশন ইনজেকশন সম্ভব না হয়।

Dagger সাবকম্পোনেন্টের মাধ্যমে প্রয়োগ করা

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

Kotlin

class LoginActivity: Activity() {
  ...

  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)
    ...
  }
}

জাভা

public class LoginActivity extends Activity {
    ...

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

        ...
    }
}

কারণ হল, login মডিউল MyApplication বা appComponent সম্পর্কে জানে না। এটি কাজ করানোর জন্য, আপনাকে ফিচার মডিউলে একটি ইন্টারফেস সংজ্ঞায়িত করতে হবে যা এমন একটি FeatureComponent প্রদান করে যা MyApplication-কে প্রয়োগ করতে হবে।

নিম্নলিখিত উদাহরণে, আপনি একটি LoginComponentProvider ইন্টারফেস সংজ্ঞায়িত করতে পারেন যা লগ-ইন ফ্লোর জন্য login মডিউলে একটি LoginComponent প্রদান করে:

Kotlin

interface LoginComponentProvider {
    fun provideLoginComponent(): LoginComponent
}

জাভা

public interface LoginComponentProvider {
   public LoginComponent provideLoginComponent();
}

এখন, LoginActivity উপরে উল্লেখ করা কোডের স্নিপেটের পরিবর্তে সেই ইন্টারফেস ব্যবহার করবে:

Kotlin

class LoginActivity: Activity() {
  ...

  override fun onCreate(savedInstanceState: Bundle?) {
    loginComponent = (applicationContext as LoginComponentProvider)
                        .provideLoginComponent()

    loginComponent.inject(this)
    ...
  }
}

জাভা

public class LoginActivity extends Activity {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        loginComponent = ((LoginComponentProvider) getApplicationContext())
                                .provideLoginComponent();

        loginComponent.inject(this);

        ...
    }
}

এখন, MyApplication-কে সেই ইন্টারফেস এবং প্রয়োজনীয় পদ্ধতি প্রয়োগ করতে হবে:

Kotlin

class MyApplication: Application(), LoginComponentProvider {
  // Reference to the application graph that is used across the whole app
  val appComponent = DaggerApplicationComponent.create()

  override fun provideLoginComponent(): LoginComponent {
    return appComponent.loginComponent().create()
  }
}

জাভা

public class MyApplication extends Application implements LoginComponentProvider {
  // Reference to the application graph that is used across the whole app
  ApplicationComponent appComponent = DaggerApplicationComponent.create();

  @Override
  public LoginComponent provideLoginComponent() {
    return appComponent.loginComponent.create();
  }
}

মাল্টি-মডিউল প্রোজেক্টে আপনি এইভাবে Dagger সাবকম্পোনেন্ট ব্যবহার করতে পারবেন। ফিচার মডিউলের ক্ষেত্রে, মডিউলগুলি একে অপরের উপর যেভাবে নির্ভর করে তার কারণে সমাধানটি আলাদা।

ফিচার মডিউলের সাথে কম্পোনেন্ট ডিপেন্ডেন্সি

ফিচার মডিউলের ক্ষেত্রে, মডিউলগুলি সাধারণত যেভাবে একে অপরের উপর নির্ভর করে তা উল্টে যায়। ফিচার মডিউল সহ app মডিউলের পরিবর্তে, ফিচার মডিউল app মডিউলের উপর নির্ভর করে। মডিউল কীভাবে সাজানো হয়েছে তা বুঝতে ছবি ২ দেখুন।

ছবি ২. ফিচার মডিউল সহ কোনও প্রোজেক্টে Dagger গ্রাফের উদাহরণ

Dagger-এ, কম্পোনেন্টকে তার সাবকম্পোনেন্ট সম্পর্কে জানতে হয়। এই তথ্য প্যারেন্ট কম্পোনেন্টে যোগ করা ড্যাগার মডিউলে অন্তর্ভুক্ত থাকে (যেমন Android অ্যাপে ড্যাগার ব্যবহার করা-এ SubcomponentsModule মডিউল)।

দুর্ভাগ্যবশত, অ্যাপ ও ফিচার মডিউলের মধ্যে রিভার্সড ডিপেন্ডেন্সি থাকার কারণে, সাবকম্পোনেন্ট app মডিউল থেকে দেখা যায় না কারণ এটি বিল্ড পাথে নেই। যেমন, login ফিচার মডিউলে নির্দিষ্ট করা LoginComponent, app মডিউলে নির্দিষ্ট করা ApplicationComponent-এর সাবকম্পোনেন্ট হতে পারে না।

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

যেমন: login নামের একটি ফিচার মডিউল LoginComponent তৈরি করতে চায় যা app Gradle মডিউলে উপলভ্য AppComponent-এর উপর নির্ভর করে।

নিচে ক্লাস এবং AppComponent-এর সংজ্ঞা দেওয়া হল যা app Gradle মডিউলের অংশ:

Kotlin

// UserRepository's dependencies
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

// UserRepository is scoped to AppComponent
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Singleton
@Component
interface AppComponent { ... }

জাভা

// UserRepository's dependencies
public class UserLocalDataSource {

    @Inject
    public UserLocalDataSource() {}
}

public class UserRemoteDataSource {

    @Inject
    public UserRemoteDataSource() { }
}

// UserRepository is scoped to AppComponent
@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;
    }
}

@Singleton
@Component
public interface ApplicationComponent { ... }

আপনার login গ্রেডেল মডিউলে, যার মধ্যে app গ্রেডেল মডিউল অন্তর্ভুক্ত আছে, আপনার একটি LoginActivity আছে, যার জন্য একটি LoginViewModel ইনস্ট্যান্স ইনজেক্ট করতে হবে:

Kotlin

// LoginViewModel depends on UserRepository that is scoped to AppComponent
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

জাভা

// LoginViewModel depends on UserRepository that is scoped to AppComponent
public class LoginViewModel {

    private final UserRepository userRepository;

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

LoginViewModel-এর UserRepository-এর উপর নির্ভরতা আছে যা উপলভ্য এবং AppComponent-এর জন্য স্কোপ করা হয়েছে। আসুন, LoginComponent তৈরি করি যা LoginActivity ইনজেক্ট করার জন্য AppComponent-এর উপর নির্ভর করে:

Kotlin

// Use the dependencies attribute in the Component annotation to specify the
// dependencies of this Component
@Component(dependencies = [AppComponent::class])
interface LoginComponent {
    fun inject(activity: LoginActivity)
}

জাভা

// Use the dependencies attribute in the Component annotation to specify the
// dependencies of this Component
@Component(dependencies = AppComponent.class)
public interface LoginComponent {

    void inject(LoginActivity loginActivity);
}

LoginComponent, AppComponent-এর উপর নির্ভরতা নির্দিষ্ট করে, এটি কম্পোনেন্ট অ্যানোটেশনের নির্ভরতা প্যারামিটারে যোগ করার মাধ্যমে করা হয়। কারণ LoginActivity Dagger-এর মাধ্যমে ইনজেক্ট করা হবে, ইন্টারফেসে inject() পদ্ধতি যোগ করুন।

LoginComponent তৈরি করার সময়, AppComponent-এর ইনস্ট্যান্স পাস করতে হবে। এটি করতে কম্পোনেন্ট ফ্যাক্টরি ব্যবহার করুন:

Kotlin

@Component(dependencies = [AppComponent::class])
interface LoginComponent {

    @Component.Factory
    interface Factory {
        // Takes an instance of AppComponent when creating
        // an instance of LoginComponent
        fun create(appComponent: AppComponent): LoginComponent
    }

    fun inject(activity: LoginActivity)
}

জাভা

@Component(dependencies = AppComponent.class)
public interface LoginComponent {

    @Component.Factory
    interface Factory {
        // Takes an instance of AppComponent when creating
        // an instance of LoginComponent
        LoginComponent create(AppComponent appComponent);
    }

    void inject(LoginActivity loginActivity);
}

এখন, LoginActivity LoginComponent-এর একটি ইনস্ট্যান্স তৈরি করতে এবং inject() পদ্ধতিকে কল করতে পারে।

Kotlin

class LoginActivity: Activity() {

    // You want Dagger to provide an instance of LoginViewModel from the Login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Gets appComponent from MyApplication available in the base Gradle module
        val appComponent = (applicationContext as MyApplication).appComponent

        // Creates a new instance of LoginComponent
        // Injects the component to populate the @Inject fields
        DaggerLoginComponent.factory().create(appComponent).inject(this)

        super.onCreate(savedInstanceState)

        // Now you can access loginViewModel
    }
}

জাভা

public class LoginActivity extends Activity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Gets appComponent from MyApplication available in the base Gradle module
        AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent;

        // Creates a new instance of LoginComponent
        // Injects the component to populate the @Inject fields
        DaggerLoginComponent.factory().create(appComponent).inject(this);

        // Now you can access loginViewModel
    }
}

LoginViewModel, UserRepository-এর উপর নির্ভর করে; এবং LoginComponent যাতে AppComponent থেকে এটি অ্যাক্সেস করতে পারে, তার জন্য AppComponent-কে এটি নিজের ইন্টারফেসে দেখাতে হবে:

Kotlin

@Singleton
@Component
interface AppComponent {
    fun userRepository(): UserRepository
}

জাভা

@Singleton
@Component
public interface AppComponent {
    UserRepository userRepository();
}

নির্ভরশীল কম্পোনেন্ট সহ স্কোপিং নিয়মগুলি সাবকম্পোনেন্টের মতোই কাজ করে। LoginComponent, AppComponent-এর ইনস্ট্যান্স ব্যবহার করে বলে, তারা একই স্কোপ অ্যানোটেশন ব্যবহার করতে পারবে না।

আপনি যদি LoginViewModel-এর স্কোপ LoginComponent-এ সেট করতে চান, তাহলে কাস্টম @ActivityScope অ্যানোটেশন ব্যবহার করে আগে যেভাবে করেছিলেন সেভাবেই করতে হবে।

Kotlin

@ActivityScope
@Component(dependencies = [AppComponent::class])
interface LoginComponent { ... }

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

জাভা

@ActivityScope
@Component(dependencies = AppComponent.class)
public interface LoginComponent { ... }

@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

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

পেশাদার পদ্ধতি

  • ApplicationComponent সবসময় app মডিউলে থাকতে হবে।

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

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

  • ফিচার মডিউল সহ Android অ্যাপে Dagger ব্যবহার করতে, app মডিউলে ApplicationComponent দ্বারা সংজ্ঞায়িত ডিপেন্ডেন্সি অ্যাক্সেস করতে কম্পোনেন্ট ডিপেন্ডেন্সি ব্যবহার করুন।