오늘도 더 나은 코드를 작성하였습니까?

Android Code Lab(Dagger2) 정리2 본문

DI/Dagger2

Android Code Lab(Dagger2) 정리2

hik14 2021. 12. 21. 11:58

app이 실행되는 기간동안 Application Graph가 메모리에서 지속적으로 남아 있어야 하기 때문에. Application Class에 Dagger Graph를  생성해야된다. graph는 app의 생명주기에 영향을 받는다. 

 

코드랩의 경우 Application Context를 주입을 필요로 하기 때문에 Application Class에 그래프를 생성한다. 

장점으로 그래프는 다른 Android 프레임워크 클래스(해당 컨텍스트로 액세스할 수 있음)에서 사용할 수 있습니다.

테스트에서 사용자 정의 Application 클래스를 사용할 수 있으므로 테스트에도 좋습니다.

 

Graph 인스턴스(예: AppComponent)를  MyApplication에 추가

open class MyApplication : Application() {

    //  the AppComponent 는 이 프로젝트의 모든 Activity 에서 사용될 수 있는 객체이다.
    val appComponent: AppComponent by lazy {
        // 팩토리 생성자를 이용하여 객체를 생성한다.
        // applicationContext 를 인자로 넘겨 주어서 graph 에서  Context 로 사용한다.
        DaggerAppComponent.factory().create(applicationContext)
    }

    open val userManager by lazy {
        UserManager(SharedPreferencesStorage(this))
    }
}

 Dagger는 프로젝트를 빌드시 AppComponent 그래프의 구현을 포함하는 DaggerAppComponent라는 클래스를 자동으로 생성한다.

@DaggerGenerated
@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class DaggerAppComponent implements AppComponent {
  private final Context context;

  private final DaggerAppComponent appComponent = this;

  private DaggerAppComponent(Context contextParam) {
    this.context = contextParam;

  }

  public static AppComponent.Factory factory() {
    return new Factory();
  }

  private SharedPreferencesStorage sharedPreferencesStorage() {
    return new SharedPreferencesStorage(context);
  }

  private UserManager userManager() {
    return new UserManager(sharedPreferencesStorage());
  }

  private RegistrationViewModel registrationViewModel() {
    return new RegistrationViewModel(userManager());
  }

  @Override
  public void inject(RegistrationActivity activity) {
    injectRegistrationActivity(activity);
  }

  private RegistrationActivity injectRegistrationActivity(RegistrationActivity instance) {
    RegistrationActivity_MembersInjector.injectRegistrationViewModel(instance, registrationViewModel());
    return instance;
  }

  private static final class Factory implements AppComponent.Factory {
    @Override
    public AppComponent create(Context context) {
      Preconditions.checkNotNull(context);
      return new DaggerAppComponent(context);
    }
  }
}

 

@Component.Factory 주석으로 Component Factory를 정의했으므로 DaggerAppComponent의 정적 메서드인 .factory()를 호출할 수 있습니다. Context(이 경우 applicationContext)를 전달하는 팩토리 내부에서 정의한 create 메소드를 호출한다. 

  private static final class Factory implements AppComponent.Factory {
    @Override
    public AppComponent create(Context context) {
      Preconditions.checkNotNull(context);
      return new DaggerAppComponent(context);
    }
  }

 

 

RegistrationActivity에서 이 위 graph Instance를 사용하여 Dagger가 @Inject로 주석이 달린 필드를 삽입하도록 할 수 있습니다. RegistrationActivity를 매개 변수로 사용하는 AppComponent의 주입 메서드를 호출한다.

 

class RegistrationActivity : AppCompatActivity() {

    @Inject
    lateinit var registrationViewModel: RegistrationViewModel

    override fun onCreate(savedInstanceState: Bundle?) {

        // Dagger 에게 의존성 주입 요청.
        (application as MyApplication).appComponent.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_registration)
        .	
        .
        .
    }
.
.
.
}

appComponent.inject(this)를 호출하여 RegistrationActivity가 @Inject로 주석을 추가한 필드(즉 registrationViewModel)가 채운다.

 

Activity을 사용할 때,

super.onCreate를 호출하기 전에 Activity의 onCreate 메소드에 Dagger를 통한 의존성 주입을 하여 Fragment restoration 문제를 막아야한다.  super.onCreate()에서 restoration 단계의 Activity는 Activity binding에 액세스하려는 Fragment을 첨부합니다.

 

버그를 찾으셨나요?  메인 페이지는 Registration 과정 후에 나타나야 합니다!  그러나 그렇지 않고 로그인 화면이 나온다

 

등록 -> 메인 (옳바름)

등록 -> 로그인 -> 메인.

 

왜요?   앱의 다른 흐름은 아직 Dagger 그래프를 사용하지 않습니다.

 

open class MyApplication : Application() {

    //  the AppComponent 는 이 프로젝트의 모든 Activity 에서 사용될 수 있는 객체이다.
    val appComponent: AppComponent by lazy {
        // 팩토리 생성자를 이용하여 객체를 생성한다.
        // applicationContext 를 인자로 넘겨 주어서 graph 에서  Context 로 사용한다.
        DaggerAppComponent.factory().create(applicationContext)
    }

	// MainActivity 에서 사용한다. 지금은
    open val userManager by lazy {
        UserManager(SharedPreferencesStorage(this))
    }
}

 

Using Dagger in the Main Flow

MainActivity가 Dagger를 사용하여 종속성을 주입 받아야 한다.

MainViewModel 및 UserManager을  주입 받을 필요가 있다. 

@Component(modules = [StorageModule::class])
interface AppComponent {

    // Factory to create instances of the AppComponent
    @Component.Factory
    interface Factory {
        //  @BindsInstance, graph 외부객체인 context 를 AppComponent 에 포함 시켜 그래프에 주입한다.
        fun create(@BindsInstance context: Context): AppComponent
    }

    // RegistrationActivity 에 주입을 필요로 하다는 것을 Dagger 에게 알림.
    // @Inject 주석이 달린 종속성 객체을 제공해야 한다.
    fun inject(activity: RegistrationActivity)
    fun inject(activity: MainActivity)
}

 

method 이름은 중요하지 않다(그래서 우리는 둘 다 injection이라고 지음) 중요한 것은 매개변수 유형입니다.

MainActivity의 Dagger에서 주입할 항목을 정의하고 그래프를 주입해 보겠습니다.

 

MainActivity.kt

class MainActivity : AppCompatActivity() {

    @Inject
    private lateinit var userManager: UserManager

    @Inject
    private lateinit var mainViewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        
        // Dagger에게 의존성 주입 요청. 
        (application as MyApplication).appComponent.inject(this)

        // 로그인 되어있으면 -> 메인페이지
        // 로그인 안되어있으면 -> 등록한적이 있음 -> 로그인 -> 메인
        //                          없음 -> 등 록  -> 메인 
        if (!userManager.isUserLoggedIn()) {
            if (!userManager.isUserRegistered()) {
                startActivity(Intent(this, RegistrationActivity::class.java))
                finish()
            } else {
                startActivity(Intent(this, LoginActivity::class.java))
                finish()
            }
        } else {
            setContentView(R.layout.activity_main)
            setupViews()
        }

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
    }
    .
    .
    .
    .
    
 }

UserManager는 이미 그래프에서 사용할 수 있으므로 Dagger는 제공하는 방법을 알고 있지만 MainViewModel은 그렇지 않습니다. Dagger가 클래스의 인스턴스를 생성하는 방법을 알 수 있도록 @Inject 주석을 생성자에 추가

 

MainViewModel.kt

class MainViewModel @Inject constructor(private val userDataRepository: UserDataRepository){
...
}

MainViewModel에는 UserDataRepository에 대한 종속성이 있으므로 @Inject로 주석을 생성자에 추가.

 

UserDataRepository.kt

class UserDataRepository @Inject constructor(private val userManager: UserManager) {
...
}

UserManager는 이미 그래프의 일부이며 Dagger는 그래프를 성공적으로 작성하는 데 필요한 모든 정보를 알고있다.

 

 

** What does it say?  error: Dagger does not support injection into private fields

 

이것이 Dagger의 단점 중 하나입니다. 주입된 filed는 최소한 패키지 전용 또는 그 이상의 가시성을 가져야 합니다.

 클래스에서 비공개(private)로 설정할 수 없습니다

 

중요: Dagger에 의해 주입되는 필드는 비공개일 수 없습니다. 최소한 패키지 전용(protected) 가시성이 있어야 합니다.

 

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var userManager: UserManager

    @Inject
    lateinit var mainViewModel: MainViewModel

    ...
}

 

Registration후  메인페이지로 이동하지 않습니다!

다시 로그인 Activity으로 이동합니다.

버그가 다시 발생하지만 왜?

 

main 흐름과 Registration 흐름 모두 애플리케이션 그래프에서 UserManager가 주입됩니다.

 

문제는 Dagger가 기본적으로 종속성을 주입할 때 항상 유형의 새 인스턴스(이 경우 UserManager)를 제공한다는 것

 Dagger가 매번 동일한 인스턴스를 재사용하도록 하려면  Scoping (범위 지정)

'DI > Dagger2' 카테고리의 다른 글

Android Code Lab(Dagger2) 정리4  (0) 2021.12.21
Android Code Lab(Dagger2) 정리3  (0) 2021.12.21
Android Code Lab(Dagger2) 정리1  (0) 2021.12.20
Android app에서 Dagger 사용  (0) 2021.05.03
Dagger Basic  (0) 2020.11.30