Skip to content

Commit

Permalink
Moved openForm(),openSavedForm and prefillAndOpenForm() to FormsInter…
Browse files Browse the repository at this point in the history
…actor (#54)

* Moved openForm(), openSavedForm() and prefillAndOpenForm() to FormsInteractor.
  • Loading branch information
shivenducs1136 authored Jun 7, 2023
1 parent 88c8371 commit 3a0017b
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 135 deletions.
18 changes: 14 additions & 4 deletions odk/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ This method opens a saved instance of a form with a given form ID, or creates a

<br><br>

`fun prefillAndOpenForm(formId: String, tagValueMap: HashMap<String, String>, context: Context)`

This method pre-fills a form with data from a given map of key-value pairs, and then opens the form in a given Android context. \
***Parameters:*** \
`formId` - A string representing the ID of the form to be opened. \
`tagValueMap` - A HashMap containing the key-value pairs representing the data to pre-fill the form with. \
`context` - A Context object representing the Android application context.

<br><br>

## FormsDatabaseInteractor Interface

The FormsDatabaseInteractor interface provides a set of methods to interact with the local forms database. This interface enables developers to manage the forms that are stored on the device, including retrieving, adding, and deleting forms.
Expand Down Expand Up @@ -260,22 +270,22 @@ Note: This creates a separate form instance of the original form and does not al

<br>

`updateForm(form: Form, tag: String, tagValue: String, listener: FormsProcessListener)`
`updateForm(formPath: String, tag: String, tagValue: String, listener: FormsProcessListener?)`

Prefills the values of a form given a tag and value or a list of tags and values. \
***Parameters:*** \
`form`- The form to update. This is an ODK Form object. \
`formPath`- A string that represents the path of the form. \
`tag`- The tag of the form element to update. This is a string value. \
`tagValue`- The new value to set for the form element. This is a string value. \
`listener`- An optional listener to handle the update process. This is a FormsProcessListener object.

<br>

`updateForm(form: Form, values: HashMap<String, String>, listener: FormsProcessListener)`
`updateForm(formPath: String, values: HashMap<String, String>, listener: FormsProcessListener?)`

Prefills the values of a form based on a list of a tags and values. \
***Parameters:*** \
`form`- The form to update. This is an ODK Form object. \
`formPath`- A string that represents the path of the form. \
`values`- A HashMap containing tag-value pairs to update the form with. This is a map from string keys to string values. \
`listener`- An optional listener to handle the update process. This is a FormsProcessListener object.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ package io.samagra.odk.collect.extension.handlers
import android.content.Context
import android.content.Intent
import android.util.Log
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.samagra.odk.collect.extension.interactors.FormInstanceInteractor
import io.samagra.odk.collect.extension.interactors.FormsDatabaseInteractor
import io.samagra.odk.collect.extension.interactors.FormsInteractor
import io.samagra.odk.collect.extension.interactors.FormsNetworkInteractor
import io.samagra.odk.collect.extension.listeners.FileDownloadListener
import io.samagra.odk.collect.extension.listeners.FormsProcessListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.javarosa.core.model.FormDef
import org.odk.collect.android.activities.FormEntryActivity
import org.odk.collect.android.events.FormEventBus
import org.odk.collect.android.events.FormStateEvent
import org.odk.collect.android.external.FormsContract
import org.odk.collect.android.formentry.loading.FormInstanceFileCreator
import org.odk.collect.android.formentry.saving.DiskFormSaver
Expand All @@ -24,6 +29,8 @@ import org.odk.collect.android.utilities.ApplicationConstants
import org.odk.collect.android.utilities.MediaUtils
import org.odk.collect.entities.EntitiesRepository
import org.odk.collect.forms.Form
import org.odk.collect.forms.instances.Instance
import org.odk.collect.forms.instances.InstancesRepository
import org.w3c.dom.Document
import java.io.File
import java.io.FileOutputStream
Expand All @@ -39,7 +46,10 @@ class ODKFormsHandler @Inject constructor(
private val formsDatabaseInteractor: FormsDatabaseInteractor,
private val storagePathProvider: StoragePathProvider,
private val mediaUtils: MediaUtils,
private val entitiesRepository: EntitiesRepository
private val entitiesRepository: EntitiesRepository,
private val formsNetworkInteractor: FormsNetworkInteractor,
private val instancesRepository: InstancesRepository,
private val formInstanceInteractor: FormInstanceInteractor
): FormsInteractor {

override fun openFormWithFormId(formId: String, context: Context) {
Expand Down Expand Up @@ -172,7 +182,6 @@ class ODKFormsHandler @Inject constructor(
})
}
}

private fun updateDocumentBasedOnTag(document: Document, tag: String, tagValue: String): Document {
try {
if (document.getElementsByTagName(tag).item(0).childNodes.length > 0)
Expand All @@ -188,4 +197,114 @@ class ODKFormsHandler @Inject constructor(
}
return document
}

override fun openForm(formId: String, context: Context) {
CoroutineScope(Job()).launch {
// Delete any saved instances of this form
val savedInstances = instancesRepository.getAllByFormId(formId)
for (instance in savedInstances) {
if (instance.status == Instance.STATUS_INCOMPLETE) {
instancesRepository.delete(instance.dbId)
}
}
val requiredForm = formsDatabaseInteractor.getLatestFormById(formId)
if (requiredForm == null) {
downloadAndOpenForm(formId, context)
}
else {
val xmlFile = File(requiredForm.formFilePath)
if (xmlFile.exists() && (requiredForm.formMediaPath == null || mediaExists(requiredForm))) {
openFormWithFormId(formId, context)
}
else {
requiredForm.formMediaPath?.let { File(it).deleteRecursively() }
xmlFile.delete()
formsDatabaseInteractor.deleteByFormId(formId)
downloadAndOpenForm(formId, context)
}
}
}
}

override fun openSavedForm(formId: String, context: Context) {
CoroutineScope(Job()).launch {
val compositeDisposable = CompositeDisposable()
compositeDisposable.add(
FormEventBus.getState()
.subscribe { event ->
when (event) {
is FormStateEvent.OnFormOpenFailed -> {
if (event.formId == formId) {
compositeDisposable.clear()
openForm(formId, context)
}
}
is FormStateEvent.OnFormOpened -> {
if (event.formId == formId) {
compositeDisposable.clear()
}
}
else -> {}
}
}
)

formInstanceInteractor.openLatestSavedInstanceWithFormId(formId, context)
}
}

override fun prefillAndOpenForm(formId: String, tagValueMap: HashMap<String, String>, context: Context) {
CoroutineScope(Job()).launch {
val compositeDisposable = CompositeDisposable()
compositeDisposable.add(FormEventBus.getState().subscribe { event ->
when (event) {
is FormStateEvent.OnFormSaved -> {
if (event.formId == formId) {
val prefilledInstance = formInstanceInteractor.getInstanceByPath(event.instancePath)
if (prefilledInstance != null) {
formInstanceInteractor.openInstance(prefilledInstance, context)
}
else {
FormEventBus.formOpenFailed(formId, "Form instance cannot be found!")
}
compositeDisposable.clear()
}
}
is FormStateEvent.OnFormOpenFailed -> if (event.formId == formId) compositeDisposable.clear()
is FormStateEvent.OnFormSaveError -> if (event.formId == formId) compositeDisposable.clear()
else -> {}
}
})
prefillForm(formId, tagValueMap)
}
}

private fun downloadAndOpenForm(formId: String, context: Context) {
formsNetworkInteractor.downloadFormById(formId, object : FileDownloadListener {
override fun onComplete(downloadedFile: File) {
openFormWithFormId(formId, context)
}
})
}

private fun mediaExists(form: Form): Boolean {
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(File(form.formFilePath))
val values = document.getElementsByTagName("value")
for (index in 0 until values.length) {
val attributes = values.item(index).attributes
if (attributes.length > 0) {
val nodeValue = attributes.item(0).nodeValue
if (nodeValue == "image" || nodeValue == "audio" || nodeValue == "video") {
var mediaFileName = values.item(index).firstChild.nodeValue
if (mediaFileName.isNotBlank()) {
mediaFileName = mediaFileName.substring(mediaFileName.lastIndexOf("/") + 1)
val mediaFile = File(form.formMediaPath + "/" + mediaFileName)
if (!mediaFile.exists())
return false
}
}
}
}
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,11 @@ class ODKHandler @Inject constructor(
): ODKInteractor {

private lateinit var formsNetworkInteractor: FormsNetworkInteractor
private lateinit var formsDatabaseInteractor: FormsDatabaseInteractor
private lateinit var formsInteractor: FormsInteractor
private lateinit var instancesRepository: InstancesRepository
private lateinit var formInstanceInteractor: FormInstanceInteractor

override fun setupODK(settingsJson: String, lazyDownload: Boolean, listener: ODKProcessListener) {
try {
ConfigHandler(application).configure(settingsJson)
formsNetworkInteractor = DaggerFormsNetworkInteractorComponent.factory().create(application).getFormsNetworkInteractor()
formsDatabaseInteractor = DaggerFormsDatabaseInteractorComponent.factory().create(application).getFormsDatabaseInteractor()
formsInteractor = DaggerFormsInteractorComponent.factory().create(application).getFormsInteractor()
instancesRepository = DaggerAppDependencyComponent.builder().application(application).build().instancesRepositoryProvider().get()
formInstanceInteractor = DaggerFormInstanceInteractorComponent.factory().create(application).getFormInstanceInteractor()

if (!lazyDownload) {
formsNetworkInteractor.downloadRequiredForms(object: FileDownloadListener {
override fun onProgress(progress: Int) { listener.onProgress(progress) }
Expand All @@ -66,113 +57,4 @@ class ODKHandler @Inject constructor(
CoroutineScope(Job()).launch{ ConfigHandler(application).reset(listener) }
}

override fun openForm(formId: String, context: Context) {
CoroutineScope(Job()).launch {
// Delete any saved instances of this form
val savedInstances = instancesRepository.getAllByFormId(formId)
for (instance in savedInstances) {
if (instance.status == Instance.STATUS_INCOMPLETE) {
instancesRepository.delete(instance.dbId)
}
}
val requiredForm = formsDatabaseInteractor.getLatestFormById(formId)
if (requiredForm == null) {
downloadAndOpenForm(formId, context)
}
else {
val xmlFile = File(requiredForm.formFilePath)
if (xmlFile.exists() && (requiredForm.formMediaPath == null || mediaExists(requiredForm))) {
formsInteractor.openFormWithFormId(formId, context)
}
else {
requiredForm.formMediaPath?.let { File(it).deleteRecursively() }
xmlFile.delete()
formsDatabaseInteractor.deleteByFormId(formId)
downloadAndOpenForm(formId, context)
}
}
}
}

override fun openSavedForm(formId: String, context: Context) {
CoroutineScope(Job()).launch {
val compositeDisposable = CompositeDisposable()
compositeDisposable.add(
FormEventBus.getState()
.subscribe { event ->
when (event) {
is FormStateEvent.OnFormOpenFailed -> {
if (event.formId == formId) {
compositeDisposable.clear()
openForm(formId, context)
}
}
is FormStateEvent.OnFormOpened -> {
if (event.formId == formId) {
compositeDisposable.clear()
}
}
else -> {}
}
}
)

formInstanceInteractor.openLatestSavedInstanceWithFormId(formId, context)
}
}

override fun prefillAndOpenForm(formId: String, tagValueMap: HashMap<String, String>, context: Context) {
CoroutineScope(Job()).launch {
val compositeDisposable = CompositeDisposable()
compositeDisposable.add(FormEventBus.getState().subscribe { event ->
when (event) {
is FormStateEvent.OnFormSaved -> {
if (event.formId == formId) {
val prefilledInstance = formInstanceInteractor.getInstanceByPath(event.instancePath)
if (prefilledInstance != null) {
formInstanceInteractor.openInstance(prefilledInstance, context)
}
else {
FormEventBus.formOpenFailed(formId, "Form instance cannot be found!")
}
compositeDisposable.clear()
}
}
is FormStateEvent.OnFormOpenFailed -> if (event.formId == formId) compositeDisposable.clear()
is FormStateEvent.OnFormSaveError -> if (event.formId == formId) compositeDisposable.clear()
else -> {}
}
})
formsInteractor.prefillForm(formId, tagValueMap)
}
}

private fun downloadAndOpenForm(formId: String, context: Context) {
formsNetworkInteractor.downloadFormById(formId, object : FileDownloadListener {
override fun onComplete(downloadedFile: File) {
formsInteractor.openFormWithFormId(formId, context)
}
})
}

private fun mediaExists(form: Form): Boolean {
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(File(form.formFilePath))
val values = document.getElementsByTagName("value")
for (index in 0 until values.length) {
val attributes = values.item(index).attributes
if (attributes.length > 0) {
val nodeValue = attributes.item(0).nodeValue
if (nodeValue == "image" || nodeValue == "audio" || nodeValue == "video") {
var mediaFileName = values.item(index).firstChild.nodeValue
if (mediaFileName.isNotBlank()) {
mediaFileName = mediaFileName.substring(mediaFileName.lastIndexOf("/") + 1)
val mediaFile = File(form.formMediaPath + "/" + mediaFileName)
if (!mediaFile.exists())
return false
}
}
}
}
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,15 @@ interface FormsInteractor {
* Note: This modifies the original form described by the form path.
*/
fun updateForm(formPath: String, values: HashMap<String, String>,listener: FormsProcessListener?)

/** Opens the latest version related to the formId. Deletes any
* saved instance of a form with this particular formId. */
fun openForm(formId: String, context: Context)

/** Opens a saved form. If no saved instance is found, opens a new form. */
fun openSavedForm(formId: String, context: Context)

/** This method pre-fills a form with data from a given map of key-value pairs,
* and then opens the form in a given Android context. */
fun prefillAndOpenForm(formId: String, tagValueMap: HashMap<String, String>, context: Context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,4 @@ interface ODKInteractor {
/** Resets everything in odk and deletes all data. */
fun resetODK(listener: ODKProcessListener)

/** Opens the latest version related to the formId. Deletes any
* saved instance of a form with this particular formId. */
fun openForm(formId: String, context: Context)

/** Opens a saved form. If no saved instance is found, opens a new form. */
fun openSavedForm(formId: String, context: Context)

fun prefillAndOpenForm(formId: String, tagValueMap: HashMap<String, String>, context: Context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import android.app.Application
import dagger.Module
import dagger.Provides
import io.samagra.odk.collect.extension.annotations.ODKFormsInteractor
import io.samagra.odk.collect.extension.components.DaggerFormInstanceInteractorComponent
import io.samagra.odk.collect.extension.components.DaggerFormsDatabaseInteractorComponent
import io.samagra.odk.collect.extension.components.DaggerFormsNetworkInteractorComponent
import io.samagra.odk.collect.extension.handlers.ODKFormsHandler
import io.samagra.odk.collect.extension.interactors.FormsInteractor
import org.odk.collect.android.injection.config.DaggerAppDependencyComponent
Expand All @@ -23,6 +25,10 @@ class FormsInteractorModule {
val storagePathProvider = appDependencyComponent.storagePathProvider()
val entitiesRepository = appDependencyComponent.entitiesRepositoryProvider().get(currentProjectProvider.getCurrentProject().uuid)
val formsDatabaseInteractor = DaggerFormsDatabaseInteractorComponent.factory().create(application).getFormsDatabaseInteractor()
return ODKFormsHandler(currentProjectProvider, formsDatabaseInteractor, storagePathProvider, mediaUtils, entitiesRepository)
val formsNetworkInteractor = DaggerFormsNetworkInteractorComponent.factory().create(application).getFormsNetworkInteractor()
val instancesRepository = DaggerAppDependencyComponent.builder().application(application).build().instancesRepositoryProvider().get()
val formInstanceInteractor = DaggerFormInstanceInteractorComponent.factory().create(application).getFormInstanceInteractor()

return ODKFormsHandler(currentProjectProvider, formsDatabaseInteractor, storagePathProvider, mediaUtils, entitiesRepository, formsNetworkInteractor,instancesRepository,formInstanceInteractor)
}
}
Loading

0 comments on commit 3a0017b

Please sign in to comment.