diff --git a/odk/extension/README.md b/odk/extension/README.md index 5e4e70dd391..9ce28649b08 100644 --- a/odk/extension/README.md +++ b/odk/extension/README.md @@ -44,6 +44,16 @@ This method opens a saved instance of a form with a given form ID, or creates a

+`fun prefillAndOpenForm(formId: String, tagValueMap: HashMap, 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. + +

+ ## 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. @@ -260,22 +270,22 @@ Note: This creates a separate form instance of the original form and does not al
-`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.
-`updateForm(form: Form, values: HashMap, listener: FormsProcessListener)` +`updateForm(formPath: String, values: HashMap, 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. diff --git a/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKFormsHandler.kt b/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKFormsHandler.kt index afff5ed7830..63ffa2b90b2 100644 --- a/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKFormsHandler.kt +++ b/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKFormsHandler.kt @@ -3,8 +3,12 @@ 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 @@ -12,6 +16,7 @@ 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 @@ -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 @@ -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) { @@ -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) @@ -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, 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 + } } diff --git a/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKHandler.kt b/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKHandler.kt index f3b9a9c31ca..e76fd7f446e 100644 --- a/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKHandler.kt +++ b/odk/extension/src/main/java/io/samagra/odk/collect/extension/handlers/ODKHandler.kt @@ -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) } @@ -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, 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 - } } diff --git a/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/FormsInteractor.kt b/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/FormsInteractor.kt index 7a52ca58ea7..374137d52bf 100644 --- a/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/FormsInteractor.kt +++ b/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/FormsInteractor.kt @@ -48,4 +48,15 @@ interface FormsInteractor { * Note: This modifies the original form described by the form path. */ fun updateForm(formPath: String, values: HashMap,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, context: Context) } diff --git a/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/ODKInteractor.kt b/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/ODKInteractor.kt index b05ff1a5b30..3bf950c32b6 100644 --- a/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/ODKInteractor.kt +++ b/odk/extension/src/main/java/io/samagra/odk/collect/extension/interactors/ODKInteractor.kt @@ -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, context: Context) } diff --git a/odk/extension/src/main/java/io/samagra/odk/collect/extension/modules/FormsInteractorModule.kt b/odk/extension/src/main/java/io/samagra/odk/collect/extension/modules/FormsInteractorModule.kt index 1e1ba23b779..bcfecac56fa 100644 --- a/odk/extension/src/main/java/io/samagra/odk/collect/extension/modules/FormsInteractorModule.kt +++ b/odk/extension/src/main/java/io/samagra/odk/collect/extension/modules/FormsInteractorModule.kt @@ -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 @@ -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) } } diff --git a/sample/src/main/java/io/samagra/oce_sample/ODKFeatureTesterActivity.kt b/sample/src/main/java/io/samagra/oce_sample/ODKFeatureTesterActivity.kt index 011e494346e..7d607d35def 100644 --- a/sample/src/main/java/io/samagra/oce_sample/ODKFeatureTesterActivity.kt +++ b/sample/src/main/java/io/samagra/oce_sample/ODKFeatureTesterActivity.kt @@ -12,6 +12,7 @@ import android.widget.Toast import io.reactivex.rxjava3.disposables.CompositeDisposable 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.interactors.ODKInteractor import io.samagra.odk.collect.extension.listeners.FileDownloadListener @@ -40,6 +41,7 @@ class ODKFeatureTesterActivity : AppCompatActivity(), View.OnClickListener { private lateinit var progressBar: ProgressBar private lateinit var odkInteractor: ODKInteractor + private lateinit var formsInteractor: FormsInteractor private lateinit var networkInteractor: FormsNetworkInteractor private lateinit var formsDatabaseInteractor: FormsDatabaseInteractor @@ -78,6 +80,7 @@ class ODKFeatureTesterActivity : AppCompatActivity(), View.OnClickListener { currentProjectProvider.getCurrentProject().name formsDatabaseInteractor = ODKProvider.getFormsDatabaseInteractor() networkInteractor = ODKProvider.getFormsNetworkInteractor() + formsInteractor = ODKProvider.getFormsInteractor() downloadFormsButton.isEnabled=true downloadAllFormsButton.isEnabled=true clearAllFormsButton.isEnabled=true @@ -124,7 +127,7 @@ class ODKFeatureTesterActivity : AppCompatActivity(), View.OnClickListener { val formId: String = openFormsInput.text.toString().trim() if (formId.isNotBlank()) { progressBar.visibility = View.VISIBLE - odkInteractor.openForm(formId, context) + formsInteractor.openForm(formId, context) } } R.id.download_form_button -> { @@ -190,7 +193,7 @@ class ODKFeatureTesterActivity : AppCompatActivity(), View.OnClickListener { val formId: String = openSavedInput.text.toString().trim() if (formId.isNotBlank()) { progressBar.visibility = View.VISIBLE - odkInteractor.openSavedForm(formId, context) + formsInteractor.openSavedForm(formId, context) } } }