Step #3: Caching the Report
To share the report with other apps, we need to write it to a file that can be served by the FileProvider
. Based on the FileProvider
metadata, that would be in a shared/
subdirectory off of the location supplied by the getCacheDir()
method on a Context
. The work to save the report to this file should be done on a background thread. When that work is done, then we can actually share the report with other apps.
Add these functions to RosterMotor
:
fun shareReport() {
viewModelScope.launch {
saveForSharing()
}
}
private suspend fun saveForSharing() {
withContext(Dispatchers.IO + appScope.coroutineContext) {
val shared = File(context.cacheDir, "shared").also { it.mkdirs() }
val reportFile = File(shared, "report.html")
val doc = FileProvider.getUriForFile(context, AUTHORITY, reportFile)
_states.value.let { report.generate(it.items, doc) }
_navEvents.emit(Nav.ShareReport(doc))
}
}
saveReport()
is simply a wrapper around saveForSharing()
, launching another coroutine. saveForSharing()
implements that coroutine, where we:
- Create the
shared
directory undergetCacheDir()
- Create a
File
object pointing to areport.html
file in thatshared
directory - Use
FileProvider.getUriForFile()
to get aUri
fromFileProvider
that maps to ourFile
- Ask our
RosterReport
to write the report to thatUri
- Post another navigation request, this time indicating that our report is ready for sharing
You will have a few compile errors. One is that AUTHORITY
is undefined. This needs to match the value that we have in the android:authorities
attribute in the <provider>
element in the manifest. That, in turn, is being created based on our application ID. So, add this constant to RosterMotor.kt
:
private const val AUTHORITY = BuildConfig.APPLICATION_ID + ".provider"
BuildConfig
is a code-generated class that contains constants associated with our build, and BuildConfig.APPLICATION_ID
is our application ID. As a result, AUTHORITY
is being assembled the same way that the android:authorities
value is being assembled.
Another compile error is that there is no Nav.ShareReport
class. Fix that by changing Nav
to look like:
sealed class Nav {
data class ViewReport(val doc: Uri) : Nav()
data class ShareReport(val doc: Uri) : Nav()
}
This adds another subclass to Nav
called ShareReport
, which also wraps a Uri
.
saveForSharing()
refers to context
in a couple of places. That needs to be a Context
, so we can use it for getCacheDir()
, and for FileProvider.getUriForFile()
. It also refers to appScope
, which is the custom CoroutineScope
that we are using for write operations. So, add two more constructor parameters to RosterMotor
for context
and appScope
:
class RosterMotor(
private val repo: ToDoRepository,
private val report: RosterReport,
private val context: Application,
private val appScope: CoroutineScope
) : ViewModel() {
We use Application
for the context
parameter to avoid an invalid Lint check complaint. Google is worried that a ViewModel
with a Context
parameter might result in a memory leak, but the Lint check cannot distinguish between valid and invalid uses of Context
. To make the Lint error go away, we use Application
, which means that our ToDoApp
will need to be the Context
.
This requires a corresponding change to ToDoApp
, adding androidApplication()
and get(named("appScope"))
to our RosterMotor
constructor call:
viewModel { RosterMotor(get(), get(), androidApplication(), get(named("appScope"))) }
androidApplication()
is functionally equivalent to androidContext()
, but it returns an Application
to satisfy compiler type checking.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.