I'm trying to inject my DialogUtils
class that requires an activity context in its constructor into my LoginActivity
. Below code is working but I'm just initializing DialogUtils
and not injecting it.
DialogUtils.kt
class DialogUtils constructor(context: Context) {
private val dialog: AlertDialog
init {
dialog = AlertDialog.Builder(context)
.setPositiveButton(R.string.ok) { dialog, _ ->
dialog.dismiss()
}
.create()
}
fun showDialog(title: String, msg: String) {
dialog.setTitle(title)
dialog.setMessage(msg)
dialog.show()
}
fun clear() {
if (dialog.isShowing) {
dialog.dismiss()
}
}
}
LoginActivity.kt
class LoginActivity : DaggerAppCompatActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val loginViewModel by lazy {
ViewModelProviders.of(this, viewModelFactory).get(LoginViewModel::class.java)
}
private lateinit var dialogUtils: DialogUtils
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
dialogUtils = DialogUtils(this)
}
override fun onPause() {
super.onPause()
dialogUtils.clear()
}
}
What I want to do is to @Inject dialogUtils: DialogUtils
in my LoginActivity
but for this I'll need activity context. How can I achieve this keeping in mind that DialogUtils
is used in many activities.
There is a flaw in your design here, that has nothing to do with DI or Dagger even: It's that your DialogUtils
class will use plain AlertDialog
s instead of DialogFragment
s. This means that dialogs generated by your class are not persistent on the screen (e.g. during activity restarts like when the device rotates) which gives a bad user experience.
So what I'd do instead is to write a custom AlertDialogFragment
implementation that communicates via callbacks with it's host (which might be an Activity
or a Fragment
), meaning that your dialog fragment implementation looks in certain places if it finds a host that implements its callback and communicates back user actions (such as button clicks, etc.). This is some utility code I usually use in my projects for this purpose:
inline fun <reified T> Fragment.requireCallback(): T =
findCallback() ?: error("No parent / target found or parent / target does not implement " + T::class.java)
inline fun <reified T> Fragment.findCallback(): T? {
val callback: Any? = parentFragment ?: targetFragment ?: context
return callback as? T
}
This could go into your AlertDialogFragment
implementation like this:
class AlertDialogFragment : DialogFragment() {
private lateinit var listener: Listener
interface Listener {
fun onDialogResult(requestCode: Int, result: AlertDialogResult)
}
override fun onAttach(context: Context) {
super.onAttach(context)
listener = requireCallback()
}
...
}
Now when you instantiate your dialog in your parent Activity
or Fragment
, you just need to let this parent implement the AlertDialogFragment.Listener
interface and you'd forward it's results to your ViewModel
- and the nice thing is: this would work across activity restarts!
You might wonder whether you want to require the callback to be implemented on the host for each dialog; my personal rule is: for one-button dialogs that mainly confirm actions (like error dialogs) I don't require the callback to be present (i.e. use the plain findCallback()
), while for special DecisionDialogFragment
implementations that have more than one button to click I make the callback usually required, so my app actually crashes if the callback is not implemented on the host as this usually means a missing piece of code on my side.