使用 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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果您使用的是 Hilt 2.28及以上版本,则可以不需要在 AndroidManifest.xml 文件的 Application 标签添加 AndroidEntryPoint 注解,而是在 HiltApplication 添加 @HiltAndroidApp 注解即可。 至于如何在代码使用 Hilt 进行依赖注入,您可以使用 @Inject 注解来标记需要注入的依赖,同时在需要使用该依赖的地方使用 @Inject 注解进行注入。此外,您还可以通过 @Module 和 @Provides 注解来自定义依赖的注入行为。 例如,以下是一个使用 Hilt 进行依赖注入的示例代码: ```java @AndroidEntryPoint public class MyActivity extends AppCompatActivity { @Inject MyDependency myDependency; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 使用注入的依赖 myDependency.doSomething(); } } @Module @InstallIn(ActivityComponent.class) public class MyModule { @Provides MyDependency provideMyDependency() { return new MyDependencyImpl(); } } ``` 在上述代码,我们使用 @AndroidEntryPoint 标注了 MyActivity,表示该 Activity使用 Hilt 进行依赖注入。通过 @Inject 注解标记了 MyDependency 字段,表示需要注入 MyDependency 类型的依赖。在 MyModule 使用 @Provides 注解提供了 MyDependency 的实例。在 MyActivity ,我们直接使用注入的 MyDependency 实例来调用其方法。 需要注意的是,上述代码是基于 Hilt 2.28及以上版本进行的示例。如果您使用的是旧版本的 Hilt,可能需要做出一些调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值