Workflow¶
Actuellement, le SDK est principalement orienté vers la construction d'applications qui fonctionnent en mode hors ligne. En bref, le SDK maintient une instance de base de données locale qui est utilisée pour effectuer le travail localement (créer des formulaires, gérer des données, ...). À la demande du client, cette base de données locale est synchronisée avec le serveur.
Voici un exemple de flux de travail typique :
- Validate server url
- Connexion
- Sync metadata: the SDK downloads a subset of the server metadata so it is available to be used at any time. Metadata sync is totally user-dependent (see Synchronization for more details)
- Télécharger les données: si vous souhaitez que les données existantes soient disponibles dans l'appareil même lorsqu'il est hors ligne, vous pouvez télécharger et sauvegarder les données de suivi et les données agrégées existantes dans l'appareil.
- Faire le travail: à ce stade, l'application est en mesure de créer les formulaires de saisie de données et d'afficher certaines données existantes. L'utilisateur peut ensuite modifier/supprimer/mettre à jour les données.
- Charger les données: de temps en temps, le travail effectué dans l'instance locale de la base de données est envoyé sur le serveur.
- Synchroniser les métadonnées: il est recommandé de synchroniser les métadonnées assez souvent pour détecter les modifications dans la configuration des métadonnées.
Validate server url¶
The first step is to validate the server url in order to know if it is a valid DHIS2 instance. The SDK exposes a method to know if the url is valid or not; if so, it returns some information for the login when available (it depends on the DHIS2 version).
This method is not bullet-proof and might return false positives: it is difficult to tell if the url is valid or not in old DHIS2 versions. In such cases, the SDK returns it as valid to continue with the login.
d2.serverModule().checkServerUrl(serverUrl)
If successful, this method will return a LoginConfig object, which includes useful information about the login, such as the application title, the country flag, the list of OidcProviders,... .
Login/Logout¶
Before interacting with the server it is required to login into the DHIS 2 instance.
d2.userModule().logIn(username, password, serverUrl)
d2.userModule().logOut()
As of version 1.6.0, the SDK supports the storage of information for multiple accounts, which means keeping a separate database for each pair user-server. Despite of that, only one account can active (or logged in) simultaneously. That means that only one user can be authenticated in only one server at the same time.
The number of maximum allowed accounts can be configured by the app (it defaults to one). A new account is automatically created after a successful login for a new pair user-server. If the number of accounts exceeds the maximum configured, the oldest account and its related database are automatically removed.
// Get the account list
d2.userModule().accountManager().getAccounts();
// Get the account for current user, or null if the user is not authenticated yet
d2.userModule().accountManager().getCurrentAccount();
// Delete account for current user
d2.userModule().accountManager().deleteCurrentAccount();
// Get/set the maximum number of accounts
d2.userModule().accountManager().getMaxAccounts();
d2.userModule().accountManager().setMaxAccounts();
The accountManager exposes an observable that emits an event when the current account is deleted. It includes the reason why the account was deleted.
// Emits an event when the current account is deleted
d2.userModule().accountManager().accountDeletionObservable();
Après une déconnexion, le SDK garde la trace du dernier utilisateur connecté afin de pouvoir différencier les utilisateurs récurrents des nouveaux utilisateurs. Il conserve également un hachage des informations d'identification de l'utilisateur afin d'authentifier l'utilisateur même en l'absence de connectivité. Ceci étant dit, la méthode de connexion sera :
- Si un utilisateur authentifié existe déjà : lancer une erreur.
- Sauf si Connecté :
- Essayez la connexion en ligne : le SDK enverra le nom d'utilisateur et le mot de passe à l'API, qui déterminera s'ils sont corrects. En cas de succès : - Si aucune base de données n'existe : créer une nouvelle base de données avec la valeur de cryptage du serveur. - S'il existe une base de données pour une autre [Url du serveur, utilisateur], supprimez-la et créez une nouvelle base de données avec la valeur de cryptage du serveur. Les données non synchronisées de l'utilisateur précédemment connecté seront définitivement perdues. - S'il existe une base de données pour la paire [URL du serveur, utilisateur] actuelle, ouvrez la base de données et cryptez ou décryptez la base de données si l'état de cryptage a changé dans le serveur.
- Si le compte de l'utilisateur a été désactivé dans le serveur : supprimez la base de données et générez une erreur.
- Sauf si Déconnecté :
- Si la paire [ URL du serveur, utilisateur ] a été la dernière à être authentifiée :
- Essayez la connexion hors ligne : le SDK vérifiera que les informations d'identification sont les mêmes que les dernières fournies, qui ont été précédemment validées par l'API.
- Si la paire [ URL du serveur, utilisateur ] n'est pas la dernière à avoir été authentifiée : lancez une erreur
L'appel aux méthodes du module ou du référentiel avant une connexion réussie ou après une déconnexion entraînera des erreurs de type "Base de données non créée".
La méthode de déconnexion supprime les informations d'identification de l'utilisateur, ce qui nécessite une nouvelle connexion avant toute interaction avec le serveur. Les métadonnées et les données sont préservées, ce qui permet à l'utilisateur de se déconnecter ou de se connecter sans perdre aucune donnée.
Login with OpenID¶
The SDK includes support for OpenID. To perform a login using OpenID an OpenIDConnectConfig is required:
OpenIDConnectConfig openIdConfig = new OpenIDConnectConfig(clientId, redirectUri, discoveryUri, authorizationUrl, tokenUrl, prompt);
It is mandatory to either provide a discoveryUri or both authorizationUrl and tokenUrl.
The prompt parameter is optional and, when provided, is forwarded to the OpenID provider as the prompt URL parameter (see the OpenID Connect specification). It accepts a space-separated list of values, e.g. "login", "select_account" or "login select_account". When set to null no prompt parameter is sent.
This configuration can be used to perform a login.
d2.userModule().openIdHandler().logIn(openIdConfig)
This call returns an IntentWithRequestCode which in an android app allows starting the OpenID login screen from the configuration provider.
startActivityForResult(intentWithRequestCode.getIntent(), intentWithRequestCode.getRequestCode());
Upon a successful login, the returned intent data can be used alongside the server url to start the sync.
d2.userModule().openIdHandler().handleLogInResponse(serverUrl, data, requestCode);
It is mandatory to include the following activity in the application Manifest file:
<activity android:name="net.openid.appauth.RedirectUriReceiverActivity"
android:exported="true"
tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="<your redirect url scheme>" />
</intent-filter>
</activity>
In order to configure all parameters check the following OpenID providers guidelines the server implements:
| OpenID Providers |
|---|
| GitHub |
| ID-porten |
| OKTA |
| KeyCloak |
| Azure AD |
| WS02 |
Two-Factor Authentication¶
The SDK now lets your app enable, disable, enter 2FA enrollment mode and query TOTP-based 2FA via TwoFactorAuthManager:
// Get the manager
val twoFactorAuthManager = d2.userModule().twoFactorAuthManager()
// Can we enroll?
twoFactorAuthManager.canTotp2faBeEnabled()
.onSuccess { allowed -> /* true = proceed */ }
.onFailure { error -> /* handle D2Error */ }
// Fetch (or auto-enroll + fetch) the secret
val secret: String = twoFactorAuthManager.getTotpSecret()
// Enable 2FA with a code from the authenticator app
twoFactorAuthManager.enable2fa("123456")
.onSuccess { resp -> /* OK */ }
.onFailure { error -> /* handle */ }
// Disable 2FA
twoFactorAuthManager.disable2fa("654321")
.onSuccess { resp -> /* OK */ }
.onFailure { error -> /* handle */ }
// Check current status (falls back to last-known value on failure)
val enabled: Boolean = twoFactorAuthManager.is2faEnabled()
getTotpSecret() will POST to /2fa/enrollTOTP2FA if you’re not already in enrollment mode, then retry the QR-secret fetch. - The SDK persists the 2FA status in the local User table so that user.twoFactorAuthEnabled() can still be used even when offline. Metadata synchronization¶
La synchronisation des métadonnées est généralement la première étape après la connexion. Elle permet de récupérer et de conserver les métadonnées nécessaires à l'utilisateur actuel. Pour lancer la synchronisation des métadonnées, nous devons exécuter :
d2.metadataModule().download();
Afin de préserver l'utilisation de la bande passante et l'espace de stockage, le SDK ne synchronise pas toutes les métadonnées du serveur, mais un sous-ensemble. Ce sous-ensemble est défini comme les métadonnées dont l'utilisateur a besoin pour effectuer des tâches de saisie de données : afficher des programmes et des ensembles de données, exécuter des règles de programme, évaluer des indicateurs de programme en ligne, etc.
Sur cette base, la synchronisation des métadonnées comprend les éléments suivants :
| Élément | La condition ou le champ d'application |
|---|---|
| L'information sur le système | Tous |
| Paramètres du système | Indicateur clé, Style clé |
| Android Settings App | General settings, Synchronization, Appearance, Analaytics |
| Paramètres de l'utilisateur | KeyDbLocale, KeyUiLocale |
| Utilisateur | Uniquement les utilisateurs authentifiés |
| Rôle d'utilisateur | Les rôles affectés aux utilisateurs authentifiés |
| Autorisations | Les pouvoirs affectés aux utilisateurs authentifiés |
| Programme | Les programmes auxquels l'utilisateur a (au moins) accès en lecture et qui sont affectés à une unité d'organisation visible par l'utilisateur |
| Types de relations | All the types visible by the user |
| Groupes d'options | Uniquement si le serveu est supérieur à 2.29 |
| EventFilters | Those related to downloaded programs |
| TrackedEntityInstanceFilters | Those related to downloaded programs |
| ProgramStageWorkingList | Those related to downloaded programs |
| Ensemble de données | Les ensembles se données auxquels l'utilisateur a (au moins) accès en lecture et qui sont affectés à une unité d'organisation visible par l'utilisateur |
| Règles de validation | Règles de validation associées aux ensembles de données |
| Unité d'organisation | Unités d'organisation dans la portée de la SAISIE ou de la RECHERCHE (y compris les descendants) |
| Groupe d'Unité d'Organisation | Groupes affectés aux unités d'organisation téléchargées |
| NIveau d'unité d'organisation | Tous |
| Constant | Tous |
| Visualisations | Visualizations assigned to Analytics settings (Android Settings App) |
| Indicateurs | Indicators assigned to downloaded dataSets and visualizations |
| Module SMS des métadonnées | Uniquement si le module SMS est activé |
Dans le cas des programmes et des ensembles de données, la synchronisation des métadonnées inclut toutes les métadonnées qui leur sont liées : étapes, sections, éléments de données, options, catégories, etc. Les éléments qui ne sont pas liés à un programme ou à un ensemble de données ne sont pas inclus.
Les configurations corrompues¶
Cette synchronisation partielle des métadonnées peut entraîner des problèmes de mauvaise configuration au niveau du serveur. Par exemple, une variable de règle de programme pointant vers un élément de données qui n'appartient plus au programme. En raison de l'utilisation de contraintes au niveau de la base de données, cette mauvaise configuration apparaîtra comme une erreur de Clé Étrangère.
Le SDK ne fait pas échouer la synchronisation, mais il stocke les erreurs dans un tableau à des fins d'inspection. Ces erreurs sont accessibles via :
d2.maintenanceModule().foreignKeyViolations()
Data states¶
Data objects have a read-only syncState property that indicates the current state of the object in terms of synchronization with the server. This state is maintained by the SDK.
Les statuts possible sont:
- SYNCRONISÉ. L'élément est synchronisé avec le serveur. Il n'y a pas de changement local pour cette valeur.
- TO_POST (à affcher). Les données créées localement qui n'existent pas encore dans le serveur.
- TO_UPDATE (à mettre à jour). Les données modifiées localement qui existent dans le serveur.
- TÉLÉCHARGEMENT. Les données sont en train d'être téléchargées. Si elles sont modifiées avant de recevoir une réponse du serveur, leur état revient à
METTRE À_JOUR. Lorsque la réponse du serveur arrive, elles ne passent pas àSYNCRONISÉ, mais restent dansMETRE À_JOURpour indiquer qu'il y a des changements locaux. - SENT_VIA_SMS. Data is sent via sms and there is no server response yet. Some servers do not have the capability to send a response, so this state means that data has been sent, but we do not know if it has been correctly imported in the server or not.
- SYNCED_VIA_SMS. Data is sent via sms and there is a successful response from the server.
- ERREUR. Les données pour lesquelles une erreur a été signalée par le serveur après le dernier téléchargement.
- AVERTISSEMENT. Les données pour lesquelles un avertissement a été émis par le serveur après le dernier téléchargement.
Additionally, in TrackedEntityInstance, Enrollment and Events we might have:
- RELATIONSHIP. This element has been downloaded with the sole purpose of fulfilling a relationship to another element. This
RELATIONSHIPelement only has basic information (uid, type, etc) and the list of TrackedEntityAttributes (in case of TrackedEntityInstances) to be able to print meaningful information about the relationship. Other data such as enrollments, events, notes, values or relationships are not downloaded. Also, this element cannot be modified or uploaded to the server.
Besides the property syncState, the classes TrackedEntityInstance, Enrollment and Events have a property called aggregatedSyncState that represents the sync state of its children. For example, if a dataValue is modified in an Event, the resulting states for the related objects would be:
| Élément | SyncState | AggregatedSyncState |
|---|---|---|
| TrackedEntityInstance | SYNCED | TO_UPDATE |
| Inscription | SYNCED | TO_UPDATE |
| Événement | TO_UPDATE | TO_UPDATE |
Tracker data¶
Téléchargement des données de suivi¶
Important
See Settings App section to know how this application can be used to control synchronization parameters.
Par défaut, le SDK ne télécharge que les instances d'entités suivies et les événements situés dans la fenêtre de saisie de l'utilisateur, mais il est également possible de télécharger les instances d'entités suivies dans la fenêtre de recherche.
Le module "entité suivie" contient le module TrackedEntityInstanceDownloader (téléchargeur d'instances d'entités suivies). Le téléchargeur suit un modèle de construction ui permet de télécharger des instances d'entités suivies en les filtrant par différents paramètres et en définissant certaines limites. Le même comportement peut être trouvé dans le module d'événement pour les événements.
Le téléchargeur suit le dernier téléchargement réussi afin d'éviter de télécharger des données non modifiées. Il utilise la pagination avec une stratégie du meilleur effort : si une page ne parvient pas à être téléchargée ou conservée, elle est ignorée et le téléchargement se poursuit avec les pages qui suivent.
Voici un exemple de son utilisation.
d2.trackedEntityModule().trackedEntityInstanceDownloader()
.[filters]
.[limits]
.download()
d2.eventModule().eventDownloader()
.[filters]
.[limits]
.download()
Actuellement, il est possible de spécifier les filtres suivants :
byProgramUid()(par Uid de programme). Filtre par l'identifiant du programme et télécharge les objets non synchronisés dans le programme.byUid()(par Uid). Filtre en fonction de l'uid de l'instance de l'entité suivie et télécharge un objet unique. Ce filtre peut être utilisé pour télécharger les instances d'entités suivies trouvées dans le champ de recherche. (Uniquement pour les instances d'entités suivies).byProgramStatus(). Filters those tracked entity instances that have a enrollment with the given status.
Le téléchargeur permet également de limiter le nombre d'objets téléchargés. Ces limites peuvent également être combinées entre elles.
limit(). Limite le nombre maximum d'objets à télécharger.limitByProgram()(limite par programme). Appliquez la limite établie à chaque programme. Le nombre d'objets qui sera téléchargé sera celui obtenu en multipliant la limite fixée par le nombre de programmes utilisateurs.limitByOrgunit()(limite par unité d'organisation). Prenez la limite établie et appliquez-la à chaque unité d'organisation. Le nombre d'objets qui sera téléchargé sera celui obtenu en multipliant la limite fixée par le nombre d'utilisateurs. sélectionnées manuellement.
Other properties:
overwrite(). By default, the SDK does not overwrite data in the device in a status other than SYNCED. If you want to overwrite the data in the device, no matter the status it has, add this method to the query chain.
L'extrait de code suivant montre un exemple d'utilisation de la fonction TrackedEntityInstanceDownloader.
d2.trackedEntityModule().trackedEntityInstanceDownloader()
.byProgramUid("program-uid")
.limitByOrgunit(true)
.limitByProgram(true)
.limit(50)
.download()
Additionally, if you want the images associated to Image data values available to be downloaded in the device, you must download them. See Dealing with FileResources section for more details.
Recherche des données Tracker¶
DHIS2 has a functionality to filter TrackedEntityInstances by related properties, like attributes, organisation units, programs or enrollment dates. The Sdk provides the TrackedEntitySearchCollectionRepository with methods that allow the download of tracked entity instances within the search scope. It can be found inside the tracked entity instance module.
The tracked entity instance search is a powerful tool that follows a builder pattern and allows the download of tracked entity instances filtering by different parameters.
d2.trackedEntityModule().trackedEntitySearch()
.[repository mode]
.[filters]
.get()
La source à partir de laquelle les TEI sont récupérés est définie par le mode de dépôt. Voici les différents modes de dépôt disponibles :
onlineOnly()(en ligne uniquement). Seules les instances d'entités suivies provenant du serveur sont renvoyées dans la liste. Une connexion Internet est nécessaire pour utiliser ce mode.offlineOnly()(hors ligne uniquement). Seules les instances d'entités suivies provenant de la base de données locale sont renvoyées dans la liste.onlineFirst()(en ligne premièrement). Les instances d'entités suivies provenant du serveur sont renvoyées en premier lieu. Lorsqu'il n'y a plus de résultats en ligne, la recherche se poursuit avec les instances d'entités suivies de la base de données locale. Une connexion Internet est nécessaire pour utiliser ce mode.offlineFirst()(hors ligne premièrement). Les instances d'entités suivies provenant de la base de données locale sont renvoyées en premier lieu. Lorsqu'il n'y a plus de résultats, la recherche se poursuit avec les instances d'entités suivies provenant du serveur. Cette méthode peut accélérer le chargement initial. Une connexion Internet est nécessaire pour utiliser ce mode.
Ce référentiel suit la même syntaxe que les autres référentiels. En outre, le référentiel propose différentes stratégies pour récupérer les données :
byAttribute()(par attribut). Cette méthode ajoute un filtre attribut à la requête. Si cette méthode est invoquée plusieurs fois, les conditions seront accompagnées d'un « connecteur AND ». Par exemple :
d2.trackedEntityModule().trackedEntitySearch()
.byAttribute("uid1").eq("value1")
.byAttribute("uid2").eq("value2")
.get()
Cela signifie que l'instance doit avoir l'attribut uid1 avec la valeur value1 ET l'attribut uid2 avec la valeur value2.
byFilter()(par filtre). Cette méthode ajoute un filtre à la requête. Si cette méthode est appelée plusieurs fois, les conditions sont jointes à l'aide d'un « connecteur AND ». Par exemple :
d2.trackedEntityModule().trackedEntitySearch()
.byFilter("uid1").eq("value1")
.byFilter("uid2").eq("value2")
.get()
Cela signifie que l'instance doit avoir l'attribut uid1 avec la valeur value1 ET l'attribut uid2 avec la valeur value2.
byQuery()(par requête). Recherche les instances d'entités suivies avec tout attribut correspondant à la requête.byDataValue(). Search tracked entity instances based on the values of their events. This filter is usually used along withprogramStage()filter.byProgram()(par programme). Filtre par programme d'inscription. Un seul programme peut être spécifié.byProgramStage(). Filter by enrollment program stage. Only one program stage can be specified.byOrgUnits()(par unités d'organisation). Filtre par unités d'organisation de l'instance d'entité suivie. Plus d'une unité d'organisation peut être spécifié.byOrgUnitMode(). Define the organisation unit mode.byProgramDate(). Define an enrollment date filter. It only applies if a program has been specified.byIncidentDate(). Define an incident date filter.byEnrollmentStatus(). Define a filter for enrollment status.byEventDate(). Define an event date filter.byEventStatus(). Define a filter for event status.byTrackedEntityType()(par type d'entité suivie). Filtre par type d'entité suivie. Un seul type peut être spécifié.byIncludeDeleted()(par l'inclusion de l'entité supprimée). Inclure ou non les instances d'entités suivies supprimées. Actuellement, ce filtre ne s'applique qu'aux instances hors ligne. instances.byStates()(par statut). Filtre par état de synchronisation. L'utilisation de ce filtre force le mode hors ligne uniquement.byFollowUp(). Filter by followUp.byAssignedUserMode(). Filter using an assignedUserMode.byLastUpdatedDate(). Define a lastUpdated filter.byTrackedEntities(). Filter by tracked entity uids.byTrackedEntityInstanceFilter(). Also know as working lists, trackedEntityInstanceFilters are a predefined set of query parameters.byProgramStageWorkingList(). Apply a ProgramStageWorkingList filter.
Exemple:
d2.trackedEntityModule().trackedEntitySearch()
.byOrgUnits().eq("orgunitUid")
.byOrgUnitMode().eq(OrganisationUnitMode.DESCENDANTS)
.byProgram().eq("programUid")
.byAttribute("attributeUid").like("value")
.offlineFirst()
Important
Les instances d'entités suivies récupérées à l'aide de ce référentiel ne sont pas conservées dans la base de données. Il est possible de les télécharger complètement en utilisant le filtre
byUid()duTrackedEntityInstanceDownloader(téléchargeur d'instances d'entités suivies) dans le module d'instances d'entités suivies.
It could happen that you add filters to the query repository in different parts of the application and you don't have a clear picture about the filters applied, specially when using working lists because they add a set of parameters. In order to solve this, you can access the filter scope at any moment in the repository:
d2.trackedEntityModule().trackedEntitySearch()
.[ filters ]
.getScope();
In addition to the standard getPaged(int) and getDataSource() methods that are available in all the repositories, the TrackedEntitySearch repository exposes a method to wrap the response in a Result object: the getResultDataSource(). This method is kind of a workaround to deal with the lack of error management in the Version 2 of the Android Paging Library (it is hardly improved in version 3). Using this dataSource you can catch search errors, such as "Min attributes required" or "Max tei count reached".
Working lists / Tracker filters¶
There are three concepts related to building a predifined filter for tracker objects:
- TrackedEntityInstanceFilters: they define filters to be used against TrackedEntity objects and have some limited capabilities to filter by event-related data, such as eventDate or eventStatus.
- EventFilters: they define filters to be used against Event objects.
- ProgramStageWorkingList: they define filters to be used against TrackedEntity objects and they add support to filter by event-related data. It is mandatory to specify a particular ProgramStage.
As usual, they have their own collection repository and can be applied in "search" repositories. For example:
// Get the filters
List<TrackedEntityInstanceFilter> filters = d2.trackedEntityModule().trackedEntityInstanceFilters().blockingGet();
List<EventFilter> filters = d2.eventModule().eventFilters().blockingGet();
List<ProgramStageWorkingList> workingLists = d2.programModule().programStageWorkingLists().blockingGet();
// Apply the filters
d2.trackedEntityModule().trackedEntitySearch()
.byTrackedEntityInstanceFilter().eq("filterUid")
.byProgramStageWorkingList().eq("workingListUid")
.get()
d2.eventModule().eventQuery()
.byEventFilter().eq("filterUid")
.get();
Ownership¶
The concept of ownership is supported in the SDK. In short, each pair trackedEntityInstance - program is owned by an organisationUnit. This ownership is used in the trackedEntityInstance search to determine the owner organisationUnit the TEI belongs to.
You can get the program owners for each trackedEntityInstance by using the repository:
d2.trackedEntityModule().trackedEntityInstances()
.withProgramOwners()
.get();
Also, you can permanently transfer the ownership by using the OwnershipManager. This transfer will be automatically uploaded to the server in the next synchronization.
d2.trackedEntityModule().ownershipManager()
.transfer(teiUid, programUid, ownerOrgunit);
Break the glass¶
The "Break the glass" concept is based on the ownership of the pair trackedEntityInstance - enrollment. If the program is PROTECTED and the user does not have DATA CAPTURE to the organisation unit, it is required to break the glass in order to read and modify the data. The workflow would be:
- Search for any tracked entity instances in SEARCH scope. It is important to not include the program uid in the query: the server will only return those TEIs that are accessible to the user, so protected TEIs in search scope won't be returned (otherwise, the user would know if the TEIs is enrolled or not without giving any reason).
- Download the TEI using the downloader and specify the TEI uid and the program uid. It is important to include both parameters to force the ownership error.
- Catch the error, if any, and check if it is an OWNERSHIP_ACCESS_DENIED error.
- If so, request the ownwership using the ownership module (see code snippet below).
- Try again the query in step 2.
TrackedEntityInstanceDownloader teiRepository = d2.trackedEntityModule().trackedEntityInstanceDownloader()
.byUid().eq(teiUid)
.byProgramUid(programUid);
try {
teiRepository.blockingDownload();
} catch (RuntimeException e) {
if (e.getCause() instanceof D2Error &&
((D2Error) e.getCause()).errorCode() == D2ErrorCode.OWNERSHIP_ACCESS_DENIED) {
// Show a dialog to the user and capture the reason to break the glass
String reason = "Reason to break the glass";
// Break the glass
d2.trackedEntityModule().ownershipManager()
.blockingBreakGlass(teiUid, programUid, reason);
// Download again
teiRepository.blockingDownload();
} else {
// Deal with other exceptions
}
}
It is recommended to upload the data immediately after if has been edited because the ownership expires in two hours (it could depend on DHIS2 versions). If the ownership has expired when the user tries to upload the data, the SDK will automatically perform a "break-the-glass" query in the background using the original reason and add the prefix "Android App sync:". In this way, an administrator could easily identify that this operation is not a real break the glass, but just an auxiliary query to perform the synchronization.
Écriture des données du tracker¶
En général, il y a deux cas différents pour gérer la création/édition/suppression de données : le cas où l'objet est identifiable (c'est-à-dire qu'il a une propriété uid) et le cas où l'objet n'est pas identifiable.
Objets identifiables (Instance d'entité suivie, Inscription, Événement). Ces référentiels ont une méthode uid() qui vous donne accès aux méthodes d'édition pour un seul objet. Si l'objet n'existe pas encore, il faut d'abord le créer. Un flux de travail typique pour créer/modifier un objet est le suivant :
- Utilisez la classe
créer une projectionpour ajouter une nouvelle instance dans le référentiel. - Enregistrer l'uid renvoyé par cette méthode.
- Utilisez la méthode
uid()avec l'uid précédent pour accéder aux méthodes d'édition.
Et dans le code, cela ressemblerait à ce qui suit :
String eventUid = d2.eventModule().events().add(
EventCreateProjection.create("enrollment", "program", "programStage", "orgUnit", "attCombo"));
d2.eventModule().events().uid(eventUid).setStatus(COMPLETED);
Objets non identifiables (Valeur de l'Attribut d'Entité Suivie, Valeur de Donnée d'Entité Suivie). Ces référentiels disposent d'une méthode value() qui permet d'accéder aux méthodes d'édition d'un seul objet. Les paramètres acceptés par cette méthode sont ceux qui identifient sans ambiguïté une valeur.
Par exemple, l'écriture d'une valeur de données d'entité suivie serait la suivante :
d2.trackedEntityModule().trackedEntityDataValues().value(eventUid, dataElementid).set(“5”);
Data values of type Image involve an additional step to create/update/read the associated file resource. More details in the Dealing with FileResources section below.
Write events in read-only TEIs¶
It is important to pay special attention to user's data access to the TEIs, enrollments and events. The SDK modify the status of the data when any write method is executed in order to upload it to the server in the next synchronization. If a user has no write data access to a particular element, the app should prevent the edition of this element.
The restrictions that must be followed by the app are these ones:
- TrackedEntityInstances: the user must have write data access to the TrackedEntityType.
- Enrollemnts: the user must have write data access to both the TrackedEntityType and the Program (this additional restriction is imposed by the SDK).
- Events: the user must have write data access to the ProgramStage.
Téléchargement des données Tracker¶
Les référentiels instance d'entité suivie et événement ont une méthode upload() (télécharger) pour respectivement télécharger des données d'entité suivie et des données d'événement (sans enregistrement). Si la portée du référentiel a été réduite par des méthodes de filtrage, seuls les objets filtrés seront téléchargés.
d2.( trackedEntityModule() | eventModule() )
.[ filters ]
.upload();
Les données dont l'état est ERROR (erreur) ou WARNING (avertissement) ne peuvent pas être téléchargées. Il est nécessaire de résoudre les erreurs avant de tenter un nouveau téléchargement : cela signifie qu'il faut modifier les données qui posent problème, ce qui ramène leur état à TO_UPDATE (à mettre à jour).
As of version 2.37, a new tracker importer was introduced (/api/tracker endpoint). The default tracker importer is still the legacy one (/api/trackedEntityInstances), but you can opt-in to use this new tracker importer by using the Android Settings webapp (see Synchronization). This is internal to the SDK; the API exposed to the app does not change.
Erreurs du tracker¶
La réponse du serveur est analysée pour s'assurer que les données ont été correctement téléchargées sur le serveur. Si la réponse du serveur contient des erreurs d'importation, celles-ci sont stockées dans la base de données, afin que l'application puisse les vérifier et prendre les mesures nécessaires pour les résoudre.
d2.importModule().trackerImportConflicts()
Les erreurs liés à une instance d'entité suivie, à une inscription ou à un événement sont automatiquement supprimées après un téléchargement réussi de l'objet.
Le SDK tente d'identifier l'élément de données ou l'attribut qui présente une erreur en analysant la réponse du serveur. Si c'est le cas, il enregistre également la valeur de l'élément lorsque l'erreur s'est produite afin que l'application puisse mettre en évidence l'élément dans le formulaire lorsque la valeur n'a pas encore été fixée.
Données tracker : valeurs réservées¶
Les attributs d'entités suivies configurés comme uniques et générés automatiquement sont générés par le serveur selon un modèle défini par l'utilisateur. Ces valeurs ne peuvent être générées que par le serveur, ce qui signifie que nous devons les mettre de côté à l'avance afin de pouvoir les utiliser lorsque nous travaillons hors ligne.
L'application est responsable de la réservation des valeurs générées avant la mise hors ligne. Cette opération peut être déclenchée par :
// Réserver des valeurs pour tous les attributs d'entités suivies uniques et générés automatiquement.
d2.trackedEntityModule().reservedValueManager().downloadAllReservedValues(numValuesToFillUp)
// Réserver des valeurs pour un attribut d'entité suivie particulier.
d2.trackedEntityModule().reservedValueManager().downloadReservedValues("attributeUid", numValuesToFillUp)
En fonction de la durée pendant laquelle l'application prévoit d'être hors ligne, elle peut décider de la quantité de valeurs à stocker. Si le modèle d'attribut dépend du code de l'unité d'organisation, le SDK va stocker des valeurs pour toutes les unités d'organisation concernées. Plus de détails sur la logique dans la Javadoc.
Les valeurs stockées peuvent être obtenues par :
d2.trackedEntityModule().reservedValueManager().getValue("attributeUid", "orgunitUid")
Données tracker : relations¶
The SDK supports all types of relationships. They are downloaded when syncing and can be accessed and created or modified.
| TEI | Inscription | Événement | |
|---|---|---|---|
| TEI | X | X | X |
| Inscription | X | X | X |
| Événement | X | X | X |
| Supported relationships |
Relationships are accessed by using the relationships module.
Interroger les relations associées à une TEI.
d2.relationshipModule().relationships().getByItem(
RelationshipHelper.teiItem("trackedEntityInstanceUid")
)
Query relationships associated to an enrollment.
d2.relationshipModule().relationships().getByItem(
RelationshipHelper.enrollmentItem("enrollmentUid")
)
Or query relationships associated to an event.
d2.relationshipModule().relationships().getByItem(
RelationshipHelper.eventItem("eventUid")
)
In the same module you can create new relationships of any type using the RelationshipHelper to model the relationship and adding them later to the relationship collection repository:
Relationship relationship = RelationshipHelper.teiToTeiRelationship("fromTEIUid", "toTEIUid", "relationshipTypeUid");
d2.relationshipModule().relationships().add(relationship);
Si l'instance d'entité suivie n'existe pas encore et que des valeurs d'attributs doivent être héritées, vous pouvez utiliser la méthode suivante pour hériter des valeurs d'attributs d'une TEI à l'autre dans le contexte d'un certain programme. Seuls les attributs marqués comme inherit seront hérités.
d2.trackedEntityModule().trackedEntityInstanceService()
.inheritAttributes("fromTeiUid", "toTeiUid", "programUid");
In order to access the dataElements and attributes associated to a relationshipConstraint, they can be accessed through the trackerDataView property as in the following examples:
relationshipType.toConstraint().trackerDataView().attributes();
relationshipType.toConstraint().trackerDataView().dataElements();
Aggregated data¶
Téléchargement de données agrégées¶
Important
See Settings App section to know how this application can be used to control synchronization parameters.
d2.aggregatedModule().data().download()
Par défaut, le SDK télécharge les valeurs de données agrégées, ensemble de données les valeurs d'enregistrement terminés et les approbations correspondant à :
- Ensembles de données : tous les ensembles de données disponibles (ceux auxquels l'utilisateur a au moins accès en lecture).
- Unités d'organisation : champ de saisie.
- Périodes : toutes les périodes disponibles, c'est-à-dire au moins :
- Jours : 60 derniers jours.
- Semaines : 13 dernières semaines (y compris les variantes du jour de départ).
- Bihebdomadaire : les 13 dernières quinzaines.
- Mensuel : 12 derniers mois.
- Bimestriel : les 6 derniers bimestres.
- Trimestres : 5 derniers trimestres.
- Semestriel : 5 derniers semestres (à compter de janvier et d'avril).
- Annuel : les 5 dernières années (y compris les variantes de l'exercice financier).
En outre, si un ensemble de données permet la saisie de données pour des périodes futures, le Sdk téléchargera les données pour ces périodes ouvertes et les stockera.
Le Sdk garde également la trace du dernier téléchargement réussi afin d'éviter de télécharger des données non modifiées du serveur.
Dans le téléchargement des approbations de données, les identifiants de combinaison d'options de flux de travail et d'attributs seront pris en compte en plus des unités d'organisation et des périodes. Les différents statuts possibles pour l'approbation des données sont les suivants :
UNAPPROVABLE(non approuvable). L'approbation des données ne s'applique pas à cette sélection. (Les données ne sont ni approuvées ni non approuvées).UNAPPROVED_WAITING(EN ATTENTE NON APPROUVÉE). Les données pourraient être approuvées pour cette sélection, mais elles attendent une approbation de niveau inférieur avant d'être prêtes à être approuvées.UNAPPROVED_ELSEWHERE(NON APPROUVÉ AILLEURS). Les données ne sont pas approuvées et sont en attente d'approbation ailleurs (elles ne peuvent pas être approuvées ici).UNAPPROVED_READY(NON APPROUVÉ, PRÊT). Les données ne sont pas approuvées et sont prêtes à être approuvées pour cette sélection.UNAPPROVED_ABOVE(NON APPROUVÉ CI-DESSUS). Les données ne sont pas approuvées ci-dessus.APPROVED_HERE(APPROUVÉ ICI). Les données sont approuvées et ont été approuvées ici (elles peuvent donc être désapprouvées ici).APPROVED_ELSEWHERE(APPROUVÉ AILLEURS). Les données sont approuvées, mais n'ont pas été approuvées ici (elles ne peuvent donc pas être désapprouvées ici).APPROVED_ABOVE(APPROUVÉ CI-DESSUS). Les données sont approuvées ci-dessus.ACCEPTED_HERE(ACCEPTÉ ICI). Les données sont approuvées et acceptées ici (elles peuvent donc être désapprouvées ici).ACCEPTED_ELSEWHERE(ACCEPTÉ AILLEURS). Les données sont approuvées et acceptées, mais ailleurs.
Les approbations de données ne sont téléchargées que pour les versions supérieures à 2.29.
Rédaction de données agrégées¶
Périodes¶
Afin de rédiger des valeurs de données ou de terminer l'enregistrement d'un ensemble de données, il est obligatoire de fournir un identifiant de période. Les périodes sont stockées dans une table de la base de données et les identifiants de période fournis doivent être déjà présents dans cette table, sinon une erreur de clé étrangère sera levée. Pour éviter cette situation, le PeriodHelper (assistant de période) est exposé à l'intérieur du PeriodModule (module de période). Avant d'ajouter des données agrégées liées à un dataSet (ensemble de données), la méthode suivante doit être appelée :
Single<List<Period>> periods = d2.periodModule().periodHelper().getPeriodsForDataSet("dataSetUid");
Cela permettra de s'assurer que : 1. L'application choisira l'une des périodes données, évitant ainsi les périodes mal formées ou erronées. 2. L'application ne pourra choisir que les périodes futures définies par le champ DataSet.openFuturePeriods. 3. L'application ne pourra sélectionner que les périodes passées définies sur la base des limites déclarées dans la section Téléchargement de données agrégées.
Valeur des données¶
DataValueCollectionRepository possède une méthode value() qui donne accès aux méthodes d'édition. Les paramètres acceptés par cette méthode sont ceux qui identifient sans ambiguïté une valeur.
DataValueObjectRepository valueRepository = d2.dataValueModule().dataValues()
.value("periodId", "orgunitId", "dataElementId", "categoryOptionComboId", "attributeOptionComboId");
valueRepository.set("value")
Enregistrement terminé de l'ensemble des données¶
Le Sdk fournit, dans le module des ensembles de données, un référentiel de collecte pour les enregistrements terminés d'ensembles de données. Ce référentiel contient des méthodes pour ajouter de nouveaux enregistrements terminés et de les supprimer.
Pour ajouter un nouvel ensemble de données à l'enregistrement terminé, il existe une méthode add() disponible :
d2.dataSetModule().dataSetCompleteRegistrations()
.add(dataSetCompleteRegistration);
Pour les supprimer de la base de données, le référentiel dispose d'une méthode value() (valeur) qui donne accès aux méthodes de suppression (delete() (supprimer) et deleteIfExist() (supprimer s'il existe)). Les paramètres acceptés par cette méthode sont ceux qui identifient sans ambiguïté que l'enregistrement de l'ensemble de données est terminé.
d2.dataSetModule().dataSetCompleteRegistrations()
.value("periodId", "orgunitId", "dataSetUid","attributeOptionCombo")
.delete()
Téléchargement de données agrégées¶
Le DataValueCollectionRepository (référentiel de collecte des valeurs de données) dispose d'une méthode upload() pour télécharger des valeurs de données agrégées.
d2.dataValueModule().dataValues().upload();
Les instances d'ensembles de données¶
Dans le SDK, une instance d'ensemble de données est une représentation pratique des données agrégées existantes. Une instance d'ensemble de données représente une combinaison unique d'ensemble de données, de période, d'unité d'organisation et de combinaison d'options d'attributs, et comprend des informations supplémentaires telles que l'état de synchronisation, le nombre de valeurs ou le nom d'affichage de certaines propriétés.
d2.dataSetModule().dataSetInstances()
.[ filters ]
.get()
// Par exemple
d2.dataSetModule().dataSetInstances()
.byDataSetUid().eq("datasetUid")
.byOrganisationUnitUid().eq("orgunitUid")
.byPeriod().in("201901", "201902")
.get() ;
Si vous n'avez besoin que d'une vue d'ensemble du statut des données agrégées, vous pouvez utiliser le référentiel DataSetInstanceSummary (résumé de l'instance de l'ensemble de données). Il accepte les mêmes filtres et retourne un nombre de DataSetInstance (instances d'ensemble de données) pour chaque combinaison.
Dealing with FileResources¶
Le SDK propose un module (le FileResourceModule) et deux assistants (le FileResourceDirectoryHelper et le FileResizerHelper) qui permettent de travailler avec des fichiers.
In the context of a mobile connection, dealing with fileResources could be high bandwidth consuming. For this reason, fileResources are not downloaded by default when downloading data and they must be explicitly downloaded if wanted. The recommendation is to download to fileResources only if it is important to have them in the device. If they are not downloaded, there is no negative consequence in terms of data integrity; the only consequence is that they are not available in the device.
On the other hand, fileResource upload is not optional: the SDK will upload all the fileResources created in the device when uploading data. This is important in order to have successful synchronizations and keep data integrity.
Module de ressources des fichiers¶
Ce module contient des méthodes pour télécharger les ressources de fichiers associées aux données téléchargées et au référentiel de la collection de ressources de fichiers de la base de données.
- Téléchargement des ressources de fichiers. The
fileResourceDownloader()offers methods to filter the fileResources we want to download. It will search for values that match the filters and whose file resource has not been previously downloaded.
d2.fileResourceModule().fileResourceDownloader()
.byDomainType().eq(FileResourceDomainType.DATA_VALUE)
.byDataDomainType().eq(FileResourceDataDomainType.TRACKER)
.byElementType().eq(FileResourceElementType.DATA_ELEMENT)
.byValueType().in(FileResourceValueType.IMAGE, FileResourceValueType.FILE_RESOURCE)
.byMaxContentLength().eq(2000000)
.download()
The SDK has a default maxContentLength of 6000000.
Après avoir téléchargé les fichiers, vous pouvez obtenir les différentes ressources de fichiers téléchargées à travers le référentiel.
-
Référentiel de collecte de ressources de fichiers. Grâce à ce référentiel, il est possible de lancer des requêtes de fichiers, d'en enregistrer de nouveaux et de les télécharger sur le serveur.
-
Get. It behaves in a similar fashion to any other SDK repository. It allows to get collections by applying different filters if desired.
d2.fileResourceModule().fileResources() .[ filters ] .get() -
Add. Pour sauvegarder un fichier, vous devez l'ajouter en utilisant la méthode
add()du référentiel en fournissant un objet de typeFile. La méthodeadd()retournera l'uid qui a été généré lors de l'ajout du fichier. Cet uid doit être utilisé pour mettre à jour la valeur de l'attribut de l'entité suivie ou la valeur des données de l'entité suivie associée à la ressource fichier.d2.fileResourceModule().fileResources() .add(file); // Single<String> The fileResource uid
Assistant de redimensionnement de fichiers¶
Le Sdk fournit une assistance pour redimensionner les fichiers images (FileResizerHelper). Cette aide contient une méthode resizeFile() qui accepte le fichier que vous souhaitez réduire et la dimension à laquelle vous souhaitez le réduire.
Les dimensions possibles sont indiquées dans le tableau suivant.
| Petit | Moyen | Grand |
|---|---|---|
| 256px | 512px | 1024px |
L'assistant prend le fichier, mesure la hauteur et la largeur de l'image, détermine lequel des deux côtés est le plus grand et réduit le plus grand des côtés à la dimension donnée et l'autre côté est mis à l'échelle à sa taille proportionnelle. La mise à l'échelle de l'image conserve toujours les proportions.
Dans le cas où la dernière image est plus petite que la dimension à laquelle vous souhaitez la redimensionner, le même fichier sera renvoyé sans être modifié.
La méthode resizeFile() renvoie un nouveau fichier situé dans le même répertoire parent que le fichier à redimensionner sous le nom resized-DIMENSION- + le nom du fichier sans redimensionnement.
Assistant de répertoire des ressources de fichiers¶
La classe d'aide FileResourceDirectoryHelper ( Assistant de répertoire des ressources de fichiers ) fournit deux méthodes.
-
getFileResourceDirectory(). This method returns aFileobject whose path points to thesdk_resourcesdirectory where the SDK will save the files associated with the file resources. -
getFileCacheResourceDirectory(). Cette méthode renvoie un objetFile(fichier) dont le chemin pointe vers le répertoiresdk_cache_resources(ressources de cache sdk). Il s'agit de l'endroit où sont stockés les fichiers volatils, tels que les photos de l'appareil photo ou les images à redimensionner. Étant donné que le répertoire est contenu dans le répertoire cache, Android peut supprimer automatiquement les fichiers du répertoire cache lorsque le système est sur le point de manquer de mémoire. Les applications tierces peuvent également supprimer des fichiers du répertoire cache. L'utilisateur peut même effacer manuellement le cache à partir des paramètres. Cependant, le fait que le cache puisse être vidé par les méthodes expliquées ci-dessus ne signifie pas que le cache sera automatiquement vidé ; par conséquent, le cache devra être nettoyé de temps en temps de manière proactive.