使用 Hilt ViewModel 中的 Activity

我一直在玩 Hilt 在一个小应用程序中对视图模型的支持,并且需要我的视图模型来启动共享活动:

@HiltViewModel
class MyCuteLittleViewModel @Inject constructor(
) : ViewModel() {

  // ... some code that invokes share()

  private fun share(content: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
      type = "text/plain"
      putExtra(Intent.EXTRA_TEXT, content)
    }
    val chooserIntent = Intent.createChooser(intent, "Share with…")

    val activity = TODO("Need an activity here!")
    activity.startActivity(chooserIntent)
  }
}

视图模型在活动配置更改中保留,因此活动不可注入,这完全有道理:在视图模型中注入活动会导致配置更改泄漏。

不幸的是,Activity该类提供了很多实用程序,因此需要访问它是相当普遍的。

大多数在线资源建议将代码移动到 Activity 或有权访问它的协作者,让其监听指示要执行的操作的事件,然后从 ViewModel 发送事件。

我不在乎这些“最佳”做法。我想要那个代码在它被使用的地方,我不想要不必要的解耦。

无论如何,这里有一点 Hilt 黑客技术可以在不更改任何Activity代码的情况下支持这一点。

首先,让我们创建一个CurrentActivityProvider作用域 to @ActivityRetainedScoped,它将负责保存当前的活动实例:

@ActivityRetainedScoped
class CurrentActivityProvider @Inject constructor() {

  // TODO Set and clear currentActivity
  private var currentActivity: Activity? = null

  fun <T> withActivity(block: Activity.() -> T) : T {
    checkMainThread()
    val activity = currentActivity
    check(activity != null) {
      "Don't call this after the activity is finished!"
    }
    return activity.block()
  }
}

然后我们可以根据需要使用它。请注意,这withActivity()使得将活动实例意外存储在错误的位置变得更加困难:

@HiltViewModel
class MyCuteLittleViewModel @Inject constructor(
  private val activityProvider: CurrentActivityProvider
) : ViewModel() {

  private fun share(content: String) {
    // ...
    activityProvider.withActivity {
      startActivity(chooserIntent)
    }
  }
}

CurrentActivityProvider.currentActivity现在我们需要为每个ActivityRetainedComponent范围进行设置。为此,我们创建了一个作用域为活动 ( ActivityComponent) 的入口点,它将提供对CurrentActivityProvider(位于父ActivityRetainedComponent作用域中的)的访问。入口点:

@EntryPoint
@InstallIn(ActivityComponent::class)
interface ActivityProviderEntryPoint {
  val activityProvider: CurrentActivityProvider
}

现在我们可以从一个活动实例中检索作用域活动提供者:

val entryPoint: ActivityProviderEntryPoint =
  EntryPointAccessors.fromActivity(this)
val activityProvider = entryPoint.activityProvider

这仅在活动是 Hilt 感知的情况下才有效,所以让我们检查它是否实现GeneratedComponentManagerHolder(🤫 它在 Hilt 的内部包中,但它也是公共的,所以🤷‍♂️),让我们为此制作一个小Activity.withProvider()实用程序:

activity.withProvider { activityProvider ->
  // TODO
}

    private fun Activity.withProvider(
      block: CurrentActivityProvider.() -> Unit
    ) {
      if (this is GeneratedComponentManagerHolder) {
        val entryPoint: ActivityProviderEntryPoint =
          EntryPointAccessors.fromActivity(this)
        val provider = entryPoint.activityProvider
        provider.block()
      }
    }

注意:Android 应用可以同时有多个处于创建状态的活动。这里的代码通过依赖ActivityRetainedComponent范围来支持这一点,这将为堆栈中的每个活动提供一个新组件,但当通过配置更改重新创建活动时仍然返回相同的逻辑组件。

现在让我们添加方法来更新生命周期更改的活动引用:

@ActivityRetainedScoped
class CurrentActivityProvider @Inject constructor() {

  private var currentActivity: Activity? = null

  fun <T> withActivity(block: Activity.() -> T) : T { /* ... */  }

  companion object {
    private fun Activity.withProvider(
      block: CurrentActivityProvider.() -> Unit
    ) { /* ... */ }

    fun onActivityCreated(activity: Activity) {
      activity.withProvider {
        currentActivity = activity
      }
    }

    fun onActivityDestroyed(activity: Activity) {
      activity.withProvider {
        if (currentActivity === activity) {
          currentActivity = null
        }
      }
    }
  }
}

最后让我们从我的Application班级挂钩生命周期回调:

@HiltAndroidApp
class MyCuteLittleApp : Application() {

  override fun onCreate() {
    super.onCreate()

    registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        CurrentActivityProvider.onActivityCreated(activity)
      }

      override fun onActivityDestroyed(activity: Activity) {
        CurrentActivityProvider.onActivityDestroyed(activity)
      }
    })
  }

}
有了这个,我们现在可以注入CurrentActivityProvider任何ActivityRetainedComponent范围(以及较低的范围)并轻松地使用activityProvider.withActivity().

测试测试 1 2 3 🎤

为了MyCuteLittleViewModel更容易测试,我们可以将共享责任转移给注入的协作者,例如Sharer:

interface Sharer {
  fun share(content: String)
}

class ActivitySharer @Inject constructor(
  private val activityProvider: CurrentActivityProvider
) : Sharer {
  override fun share(content: String) {
    // ...
    activityProvider.withActivity {
      startActivity(chooserIntent)
    }
  }
}

@Module
@InstallIn(ActivityRetainedComponent::class)
interface SharerModule {
  @Binds fun bindSharer(sharer: ActivitySharer): Sharer
}
### 如何在 Android使用 Hilt 进行依赖注入 #### 1. 添加依赖 为了在项目中使用 Hilt,首先需要配置项目的 `build.gradle` 文件。这包括添加必要的插件以及依赖库。 在根目录下的 `build.gradle` 文件中添加以下内容: ```gradle plugins { id 'com.android.application' id 'kotlin-android' id 'dagger.hilt.android.plugin' // 添加 Hilt 插件 } ``` 在模块级别的 `build.gradle` 文件中添加以下依赖项: ```gradle dependencies { implementation "com.google.dagger:hilt-android:2.44" // 使用最新版本号替换 kapt "com.google.dagger:hilt-android-compiler:2.44" // 如果需要支持 ViewModel 注入 implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" kapt "androidx.hilt:hilt-compiler:1.0.0-alpha03" } ``` 以上步骤确保了 Hilt 能够正常工作并生成所需的代码[^1]。 --- #### 2. 配置 Application 类 为了让 Hilt 管理整个应用程序的依赖关系,需要让自定义的应用程序类继承 `HiltAndroidApp` 并加上相应的注解: ```java package com.example.myapp; import android.app.Application; import dagger.hilt.android.HiltAndroidApp; @HiltAndroidApp public class MyApplication extends Application {} ``` 此操作会初始化 Hilt 容器,并将其绑定到应用生命周期。 --- #### 3. 创建可注入的对象 假设有一个简单的接口及其实现用于日志记录功能: ```java // 接口声明 public interface AnalyticsAdapter { void logEvent(String event); } // 实现类 public class FirebaseAnalyticsAdapter implements AnalyticsAdapter { @Override public void logEvent(String event) { System.out.println("Logging Event: " + event); } } ``` 接着,在该实现类上添加 `@Singleton` 或其他适当的作用域注解来表明其生命周期管理方式[^3]。 --- #### 4. 提供依赖实例 创建一个带有 `@Module` 和 `@InstallIn` 注解的类以告诉 Hilt 如何提供特定类型的实例: ```java import dagger.Module; import dagger.Provides; import dagger.hilt.InstallIn; import dagger.hilt.components.SingletonComponent; import javax.inject.Singleton; @Module @InstallIn(SingletonComponent.class) public class AppModule { @Provides @Singleton public static AnalyticsAdapter provideAnalyticsAdapter() { return new FirebaseAnalyticsAdapter(); } } ``` 这段代码表示每当请求 `AnalyticsAdapter` 的时候都会返回一个新的 `FirebaseAnalyticsAdapter` 实例[^2]。 --- #### 5. 注入依赖至 Activity/ViewModel 对于视图模型中的依赖注入可以通过 `@ViewModelInject` 构造函数完成;而对于活动或者片段则需标记它们为入口点(`@AndroidEntryPoint`)以便启用 DI 功能。 下面展示了一个例子,其中展示了如何在一个登录活动中获取已准备好的视图模型: ```java import androidx.lifecycle.ViewModel; import dagger.hilt.android.lifecycle.HiltViewModel; import javax.inject.Inject; @HiltViewModel public class LoginViewModel extends ViewModel { private final AnalyticsAdapter analyticsAdapter; @Inject public LoginViewModel(AnalyticsAdapter analyticsAdapter) { this.analyticsAdapter = analyticsAdapter; } public void performLoginAction() { analyticsAdapter.logEvent("User logged in"); } } @AndroidEntryPoint public class LoginActivity extends AppCompatActivity { private final LoginViewModel loginViewModel; public LoginActivity(LoginViewModel viewModel) { this.loginViewModel = viewModel; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 此处可以直接访问loginViewModel对象 loginViewModel.performLoginAction(); } } ``` 这里的关键在于利用了 Kotlin 属性委托简化了对 ViewModels 访问过程的同时也完成了必要组件装配工作[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值