跳转至
For the complete DHIS2 documentation index, see llms.txt.

工作流程{ #android_sdk_workflow }

目前,SDK主要面向构建可在离线模式下运行的应用程序。简而言之,SDK维护一个本地数据库实例,该实例用于在本地完成工作(创建表单,管理数据等)。当客户端请求时,此本地数据库与服务器同步。

典型的工作流程如下:

  1. 验证服务器网址
  2. 登录
  3. 同步元数据: SDK 会下载服务器元数据的子集,以便随时使用。元数据同步完全取决于用户(详见 Synchronization
  4. **下载数据:**如果您希望即使脱机也可以在设备中使用现有数据,则可以下载并保存设备中的现有跟踪器和汇总数据。
  5. **执行工作:**此时,该应用程序能够创建数据输入表单并显示一些现有数据。然后,用户可以编辑/删除/更新数据。
  6. **上传数据:**有时会在本地数据库实例中完成的工作发送到服务器。
  7. **同步元数据:**建议经常同步元数据以检测元数据配置中的更改。

验证服务器 url{ #validate-server-url }

第一步是验证服务器网址,以确定它是否是一个有效的 DHIS2 实例。SDK 提供了一种方法来确定 url 是否有效;如果有效,它会返回一些登录信息(取决于 DHIS2 版本)。

该方法并非无懈可击,可能会返回误报:在旧版本的 DHIS2 中,很难判断 url 是否有效。在这种情况下,SDK 会返回有效,以便继续登录。

d2.serverModule().checkServerUrl(serverUrl)

如果成功,该方法将返回一个 LoginConfig 对象,其中包括有关登录的有用信息,如应用程序标题、国旗、OidcProviders 列表....

登录/注销

在与服务器交互之前,需要登录 DHIS 2 实例。

d2.userModule().logIn(username, password, serverUrl)

d2.userModule().logOut()

从 1.6.0 版开始,SDK 支持为多个账户存储信息,这意味着为每对用户服务器保存一个单独的数据库。尽管如此,同一时间只能有一个账户处于活动状态(或登录)。也就是说,同一时间只能有一个用户在一个服务器上进行身份验证。

允许的最大账户数可由应用程序配置(默认为一个)。新配对用户服务器登录成功后,会自动创建一个新账户。如果账户数超过了配置的上限,则会自动删除最老的账户及其相关数据库。

// 获取账户列表
d2.userModule().accountManager().getAccounts();

// 获取当前用户的账户,如果用户尚未通过身份验证,则获取空账户
d2.userModule().accountManager().getCurrentAccount();

// 删除当前用户的账户
d2.userModule().accountManager().deleteCurrentAccount();

// 获取/设置账户的最大数量
d2.userModule().accountManager().getMaxAccounts();
d2.userModule().accountManager().setMaxAccounts();

accountManager 公开了一个可观察对象,当当前账户被删除时,它会发出一个事件。其中包括删除账户的原因。

Java // 当前账户删除时发出事件 d2.userModule().accountManager().accountDeletionObservable();

注销后,SDK会跟踪上次登录的用户,以便能够区分重复用户和新用户。即使没有连接,它也保留用户凭据的哈希值以对用户进行身份验证。鉴于以上所述,登录方法将:

- 如果已通过身份验证的用户已经存在:抛出错误。
- Else if *Online*:
  - 尝试**在线登录**:SDK会将用户名和密码发送到API,这将确定它们是否正确。如果成功:
        -如果不存在数据库:从服务器创建具有加密值的新数据库。
        -如果存在另一个[serverUrl,user]的数据库,则将其删除并从服务器创建具有加密值的新数据库。先前登录用户的未同步数据将永久丢失。
        -如果存在用于当前[serverUrl,user]对的数据库,请打开数据库,如果服务器中的加密状态已更改,则对数据库进行加密或解密。
  - 如果服务器中已禁用用户帐户:删除数据库并引发错误。
- 否则,如果 *脱机*:
  - 如果[serverUrl,user]对是最后经过身份验证的:
    - 尝试**离线登录**:SDK 将验证凭据是否与上次提供的相同,而上次提供的凭据之前已通过 API 验证。
  - 如果[serverUrl,user]对不是最后经过身份验证的:抛出错误

在成功登录之前或注销之后调用模块或存储库方法将导致“未创建数据库”错误。

注销方法会删除用户凭据,因此在与服务器进行任何交互之前都需要重新登录。元数据和数据得以保留,因此用户可以注销/登录而不会丢失任何信息。

## 使用 OpenID 登录{ #android_sdk_login_open_id }

SDK 支持 OpenID。要使用 OpenID 进行登录,需要一个 OpenIDConnectConfig:

```java
OpenIDConnectConfig openIdConfig = new OpenIDConnectConfig(clientId, redirectUri, discoveryUri, authorizationUrl, tokenUrl, prompt);

必须提供 discoveryUri 或同时提供 authorizationUrl 和 tokenUrl。

prompt 参数是可选的,如果提供了该参数,它将作为 prompt URL 参数转发给 OpenID 提供商(参见 OpenID Connect 规范)。 该参数接受以空格分隔的值列表,例如 "login"、"select_account" 或 "login select_account"。若设置为 null,则不会发送任何提示参数。

此配置可用于登录。

d2.userModule().openIdHandler().logIn(openIdConfig)

此调用会返回一个 IntentWithRequestCode,在 Android 应用中,它允许从配置提供商处启动 OpenID 登录界面。

startActivityForResult(intentWithRequestCode.getIntent(), intentWithRequestCode.getRequestCode());

登录成功后,可将返回的意图数据与服务器 URL 结合使用,以启动同步。

d2.userModule().openIdHandler().handleLogInResponse(serverUrl, data, requestCode);

必须在应用程序的清单文件中包含以下活动:

<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>

要配置所有参数,请查阅服务器所实现的以下 OpenID 提供商指南:

OpenID 提供商
Google
GitHub
ID-porten
OKTA
KeyCloak
Azure AD
WS02

双因素认证

现在,该 SDK 允许您的应用通过 TwoFactorAuthManager 启用、禁用、进入双因素认证注册模式以及查询基于 TOTP 的双因素认证:

// 获取管理器
val twoFactorAuthManager = d2.userModule().twoFactorAuthManager()

// 能否进行注册?
twoFactorAuthManager.canTotp2faBeEnabled()
  .onSuccess { allowed -> /* true = 继续 */ }
  .onFailure { error -> /* 处理 D2Error */ }

// 获取(或自动注册并获取)密钥
val secret: String = twoFactorAuthManager.getTotpSecret()

// 使用身份验证器应用生成的验证码启用双因素认证
twoFactorAuthManager.enable2fa("123456")
  .onSuccess { resp -> /* 成功 */ }
  .onFailure { error -> /* 处理 */ }

// 禁用双因素认证
twoFactorAuthManager.disable2fa("654321")
  .onSuccess { resp -> /* 成功 */ }
  .onFailure { error -> /* 处理 */ }

// 检查当前状态(失败时回退到已知的最新值)
val enabled: Boolean = twoFactorAuthManager.is2faEnabled()
行为记录: - 如果当前尚未处于注册模式,getTotpSecret() 将向 /2fa/enrollTOTP2FA 发送 POST 请求,然后重试获取 QR 密钥。 - 该 SDK 会将双因素认证状态持久化存储在本地 User 表中,因此即使在离线状态下,仍可使用 user.twoFactorAuthEnabled() 方法。

元数据同步

登录后,第一步通常是元数据同步。它获取并保留当前用户所需的元数据。要启动元数据同步,我们必须执行:

d2.metadataModule().download();

为了节省带宽使用量和存储空间,SDK不同步服务器中的所有元数据,而是同步子集。此子集定义为用户执行数据输入任务所需的元数据:渲染程序和数据集,执行程序规则,评估内联程序指示器等。

基于此,元数据同步包括以下元素:

元件 条件或范围
系统信息 所有
系统设置 KeyFlag,KeyStyle
Android“设置”应用 “常规设置”、“同步”、“外观”、“分析”
用户设置 KeyDbLocale,KeyUiLocale
Sharing of data store keys follows the same principle as for other metadata sharing (see
Sharing). 仅经过身份验证的用户
用户角色 分配给已验证用户的角色
权威 分配给已验证用户的权限
IndicatorGroup 用户具有(至少)读取数据访问权并将其分配给用户可见的任何组织单位的程序
关系类型 用户可见的所有类型
选项组 仅当服务器大于2.29
事件过滤器 与下载的程序相关的
TrackedEntityInstanceFilters 与下载的程序相关的
ProgramStageWorkingList 与下载的程序相关的
DataElementGroup 用户具有(至少)读取数据访问权限并已分配给该用户可见的任何组织单位的数据集
CATEGORY_OPTION 与数据集关联的验证规则
DataElementCategory CAPTURE或SEARCH范围内的组织单位(包括后代)
DataElementCategoryCombo 分配给已下载组织的组
组织单位级别 所有
N{<indicator-id>} 所有
票证和验证结果通知 { #webapi_messaging_tickets } 分配给“分析”设置的可视化图表(Android“设置”应用)
指标 分配给已下载数据集和可视化图表的指标
短信模块元数据 仅在启用SMS模块时

对于程序和数据集,元数据同步包括与之相关的所有元数据:阶段,节,数据元素,选项,类别等。与任何程序或数据集无关的那些元素均不包括在内。

配置损坏

这种部分元数据同步可能会暴露服务器端配置错误问题。例如,指向不再属于程序的DataElement的ProgramRuleVariable。由于使用了数据库级别的约束,这种配置错误将显示为外键错误。

SDK不会使同步失败,但是会将错误存储在表格中以进行检查。可以通过以下方式访问这些错误:

d2.maintenanceModule().foreignKeyViolations()

数据状态

数据对象有一个只读的 syncState 属性,用于指示该对象与服务器同步的当前状态。该状态由 SDK 进行维护。

可能的状态是:

  • 已同步。元素已与服务器同步。此值没有本地更改。
  • TO_POST. Data created locally that does not exist in the server yet.
  • TO_UPDATE. Data modified locally that exists in the server.
  • 正在上传。数据正在上传。如果在收到服务器响应之前对数据进行了修改,其状态将恢复为 TO_UPDATE。当服务器响应到达时,其状态不会变为 SYNCED,而是保持在 TO_UPDATE 状态,以表明存在本地更改。
  • SENT_VIA_SMS。数据已通过短信发送,但尚未收到服务器响应。部分服务器不具备发送响应的能力,因此该状态表示数据已发送,但我们尚不清楚数据是否已正确导入服务器。
  • SYNCED_VIA_SMS。数据已通过短信发送,且已收到服务器的成功响应。
  • 错误。上次上传后从服务器接收到错误的数据。
  • 警告。上次上传后从服务器收到警告的数据。

此外,在 TrackedEntityInstance、Enrollment 和 Events 中,我们可能会有:

  • 关系。 该元素的下载仅出于与另一个元素建立关联之目的。此 RELATIONSHIP 元素仅包含基本信息(uid、type 等)以及 TrackedEntityAttributes 列表(针对 TrackedEntityInstances 时),以便能够输出有关该关联的有意义的信息。 其他数据(如注册信息、事件、备注、数值或关系)均未下载。此外,该元素无法被修改或上传至服务器。

除了 syncState 属性外,TrackedEntityInstance、Enrollment 和 Events 类还拥有一个名为 aggregatedSyncState 的属性,该属性表示其子节点的同步状态。例如,如果在 Event 中修改了一个 dataValue,则相关对象的最终状态将如下所示:

元件 SyncState 聚合同步状态
TrackedEntityInstance SYNCED TO_UPDATE
Shallow copy { #shallow-copy } SYNCED TO_UPDATE
合并策略(DISCARD 或 LAST_UPDATED) 更新 TO_UPDATE

跟踪器数据

追踪器数据下载

重要

请参阅 设置应用 部分,了解如何使用该应用控制同步参数。

默认情况下,SDK仅下载TrackedEntityInstances和Events 位于用户捕获范围内,但也可以 在搜索范围内下载TrackedEntityInstances。

“受追踪实体”模块包含 TrackedEntityInstanceDownloader。该下载器遵循构建器 模式,允许通过 不同参数**对受追踪实体实例进行筛选,并可定义一些**限制。在事件模块中,针对事件也提供了 相同的功能。

下载程序会跟踪最新的成功下载,以避免 下载未修改的数据。它尽力使用分页 策略:如果页面无法下载或持久保存,则 跳过,它将继续下一页。

这是如何使用它的一个例子。

d2.trackedEntityModule().trackedEntityInstanceDownloader()
    .[filters]
    .[limits]
    .download()
d2.eventModule().eventDownloader()
    .[filters]
    .[limits]
    .download()

当前,可以指定以下过滤器:

  • byProgramUid()。根据程序 UID 进行筛选,并下载尚未同步的 程序中的对象。
  • byUid()。按跟踪的实体实例uid过滤并下载一个 唯一对象。该过滤器可用于下载跟踪的实体 在搜索范围内找到的实例。(仅适用于受追踪的实体 实例)。
  • byProgramStatus()。筛选出具有指定状态的注册记录的受追踪实体实例。

下载器还允许限制下载对象的数量。 这些限制也可以相互组合。

  • limit()。限制要下载的最大对象数。
  • limitByProgram()。将设定的限制应用于每个 程序。将要下载的对象数量将是 通过将该限值乘以用户程序的数量而得出。
  • limitByOrgunit()。将设定的限制应用于每个 组织单位。将要下载的对象数量 是通过将设置的限制乘以用户数量而获得的 组织单位。

其他属性:

  • overwrite()。默认情况下,SDK 不会覆盖处于 SYNCED 状态以外的设备中的数据。如果您希望覆盖设备中的数据(无论其处于何种状态),请将此方法添加到查询链中。

下面的代码片段展示了一个 TrackedEntityInstanceDownloader 的使用示例。

d2.trackedEntityModule().trackedEntityInstanceDownloader()
    .byProgramUid("program-uid")
    .limitByOrgunit(true)
    .limitByProgram(true)
    .limit(50)
    .download()

此外,如果您希望与 Image 数据值关联的图片可在设备上下载,则必须先将它们下载到设备上。更多详细信息,请参阅 处理 FileResources 部分。

DHIS2 提供了一项功能,可根据相关 属性(如特征、组织单位、项目或注册 日期)对 TrackedEntityInstances 进行筛选。 SDK 提供了 TrackedEntitySearchCollectionRepository, 其方法允许下载搜索范围内的 受追踪实体实例。该组件位于受追踪实体实例模块中。

“受监视实体实例搜索”是一个功能强大的工具,它遵循 构建器模式,并允许根据**不同参数**过滤 受监视实体实例并进行下载。

d2.trackedEntityModule().trackedEntitySearch()
    .[repository mode]
    .[filters]
    .get()

TEI 的获取来源由 存储库模式 决定。 以下是可用的不同存储库模式:

  • onlineOnly()。仅来自服务器的TrackedEntityInstances是 返回在列表中。使用此模式需要互联网连接。
  • offlineOnly()。仅来自本地的TrackedEntityInstances 列表中返回数据库。
  • onlineFirst()。来自服务器的 TrackedEntityInstances 是 排名第一。一旦网上没有更多结果了,它 接着处理本地数据库中的 TrackedEntityInstances。互联网 使用此模式需要连接。
  • offlineFirst()。来自本地数据库的 TrackedEntityInstances 返回第一位。一旦没有其他结果,它将继续 其中 TrackedEntityInstances 来自服务器。此方法可能会 加快初始加载速度。使用此功能需要连接互联网。 模式。

该存储库采用与其他存储库相同的语法。 此外,该存储库还提供了多种数据获取策略:

  • byAttribute()。该方法会在查询中添加一个 属性 过滤器。 如果多次调用此方法,则条件会以“AND”连接起来 连接器。例如:
d2.trackedEntityModule().trackedEntitySearch()
    .byAttribute("uid1").eq("value1")
    .byAttribute("uid2").eq("value2")
    .get()

这意味着该实例必须具有属性 uid1,且其值为 value1 且 属性 uid2 的值为 value2。

  • byFilter()。此方法向查询添加* filter *。如果这 方法被多次调用,条件附加一个AND 连接器。例如:
d2.trackedEntityModule().trackedEntitySearch()
    .byFilter("uid1").eq("value1")
    .byFilter("uid2").eq("value2")
    .get()

这意味着该实例必须具有属性 uid1,且其值为 value1 且 属性 uid2 的值为 value2。

  • byQuery()。根据**任意**属性搜索被追踪的实体实例 匹配查询。
  • byDataValue()。根据事件的值搜索被追踪的实体实例。该筛选器通常与 programStage() 筛选器配合使用。
  • byProgram()。按注册程序过滤。只能有一个程序 / api / dataValueSets?dataSet = pBOMPrpg1QX&orgUnit = DiszpKrYNg8&lastUpdatedDuration = 10d
  • byProgramStage()。按注册项目阶段进行筛选。只能指定一个项目阶段。
  • byOrgUnits()。按受监控实体实例的组织单位进行筛选。 可以指定多个组织单位。
  • byOrgUnitMode()。定义组织单位模式。
  • byProgramDate()。定义一个注册日期筛选条件。该筛选条件仅在指定了项目时才生效。
  • byIncidentDate()。定义一个事件日期筛选器。
  • byEnrollmentStatus()。定义一个用于筛选注册状态的过滤器。
  • byEventDate()。定义一个活动日期筛选器。
  • byEventStatus()。为事件状态定义一个过滤器。
  • byTrackedEntityType()。按 TrackedEntityType 过滤。仅限一种类型 可以指定。
  • byIncludeDeleted()。是否包含已被删除的受监控实体 实例。目前,此过滤器仅适用于 离线 实例。
  • byStates()。按同步状态过滤。使用此滤镜力 **仅限离线**模式。
  • byFollowUp()。按 followUp 过滤。
  • byAssignedUserMode()。使用 assignedUserMode 进行过滤。
  • byLastUpdatedDate()。定义一个“最后更新时间”筛选条件。
  • byTrackedEntities()。按受监控实体的 UID 进行筛选。
  • byTrackedEntityInstanceFilter()。跟踪实体实例过滤器(trackedEntityInstanceFilters)也称为**工作列表**,是一组预定义的查询参数。
  • byProgramStageWorkingList()。应用 ProgramStageWorkingList 筛选条件。

<attribute id>

d2.trackedEntityModule().trackedEntitySearch()
                .byOrgUnits().eq("orgunitUid")
                .byOrgUnitMode().eq(OrganisationUnitMode.DESCENDANTS)
                .byProgram().eq("programUid")
                .byAttribute("attributeUid").like("value")
                .offlineFirst()

重要提示

使用此存储库检索的 TrackedEntityInstances 不会保存在数据库中。可以通过 “被追踪实体实例”模块中的 TrackedEntityInstanceDownloader 的 byUid() 过滤器将其完全下载。

您可能会在应用程序的不同部分向查询存储库添加过滤器,却无法清楚地了解已应用的过滤器情况,尤其是在使用工作列表时,因为它们会添加一组参数。为了解决这个问题,您可以在存储库中随时查看过滤器作用域:

d2.trackedEntityModule().trackedEntitySearch()
    .[ filters ]
    .getScope();

除了所有存储库中都提供的标准 getPaged(int) 和 getDataSource() 方法外,TrackedEntitySearch 存储库还提供了一个方法,用于将响应封装到 Result 对象中:即 getResultDataSource()。 该方法是一种变通方案,用于应对 Android 分页库第 2 版中错误管理机制的缺失(第 3 版在这方面几乎没有改进)。使用此 dataSource,您可以捕获诸如“属性数量不足”或“TEI 计数已达上限”之类的搜索错误。

工作清单 / 追踪器筛选条件{ #working-lists-tracker-filters }

与为追踪器对象构建预定义过滤器相关的概念有三个:

  • TrackedEntityInstanceFilters:它们定义了用于对 TrackedEntity 对象进行筛选的过滤器,并具备根据事件相关数据(如 eventDate 或 eventStatus)进行筛选的有限功能。
  • EventFilters:它们定义了用于对 Event 对象进行过滤的过滤器。
  • ProgramStageWorkingList:它们定义了用于对 TrackedEntity 对象进行筛选的过滤器,并增加了根据事件相关数据进行筛选的支持。必须指定一个具体的 ProgramStage。

和往常一样,它们都有自己的集合仓库,并可应用于“搜索”仓库。例如:

// 获取过滤器
List<TrackedEntityInstanceFilter> filters = d2.trackedEntityModule().trackedEntityInstanceFilters().blockingGet();
List<EventFilter> filters = d2.eventModule().eventFilters().blockingGet();
List<ProgramStageWorkingList> workingLists = d2.programModule().programStageWorkingLists().blockingGet();

// 应用过滤器
d2.trackedEntityModule().trackedEntitySearch()
    .byTrackedEntityInstanceFilter().eq("filterUid")
    .byProgramStageWorkingList().eq("workingListUid")
    .get()

d2.eventModule().eventQuery()
    .byEventFilter().eq("filterUid")
    .get();

所有权{ #ownership }

SDK 支持所有权概念。简而言之,每个 trackedEntityInstance-程序对都归属于一个 organizationUnit。在 trackedEntityInstance 搜索中,会利用这种所有权关系来确定该 TEI 所属的拥有者 organizationUnit。

您可以通过使用存储库来获取每个 trackedEntityInstance 的程序所有者:

d2.trackedEntityModule().trackedEntityInstances()
        .withProgramOwners()
        .get();

此外,您还可以使用 OwnershipManager 永久转让所有权。该转让操作将在下次同步时自动上传至服务器。

d2.trackedEntityModule().ownershipManager()
        .transfer(teiUid, programUid, ownerOrgunit);

打破玻璃{ #break-the-glass }

“打破玻璃”的概念基于 trackedEntityInstance - enrollment 这对实体之间的所有权关系。如果该程序处于 受保护 状态,且用户对该组织单位不具备 数据捕获 权限,则必须“打破玻璃”才能读取和修改数据。 工作流如下:

  1. 在 SEARCH 范围内搜索任何受追踪的实体实例。 请务必不要在查询中包含程序 UID:服务器只会返回用户可访问的 TEI,因此搜索范围内的受保护 TEI 不会被返回(否则,用户将无需提供任何理由即可得知该 TEI 是否已注册)。
  2. 使用下载器下载 TEI 文件,并指定 TEI uid 和 程序 uid。必须同时包含这两个参数,才能触发所有权错误。
  3. 捕获错误(如有),并检查是否为 OWNERSHIP_ACCESS_DENIED 错误。
  4. 如果是这样,请使用所有权模块申请所有权(参见下面的代码片段)。
  5. 请重新执行步骤 2 中的查询。
TrackedEntityInstanceDownloader teiRepository = d2.trackedEntityModule().trackedEntityInstanceDownloader()
        .byUid().eq(teiUid)
        .byProgramUid(programUid);

try {
    teiRepository.blockingDownload();
} catch (RuntimeException e) {
    if (e.getCause() 属于 D2Error 且
            ((D2Error) e.getCause()).errorCode() == D2ErrorCode.OWNERSHIP_ACCESS_DENIED) {
        // 向用户显示对话框并记录“打破玻璃”的原因
        String reason = "打破玻璃的原因";

        // 打破玻璃
        d2.trackedEntityModule().ownershipManager()
                .blockingBreakGlass(teiUid, programUid, reason);

        // 重新下载
        teiRepository.blockingDownload();
    } else {
        // 处理其他异常
    }
}

建议在编辑完数据后立即上传,因为所有权将在两小时后过期(具体时间可能因DHIS2版本而异)。 如果用户尝试上传数据时所有权已过期,SDK 将在后台自动使用原始原因执行“紧急查询”,并在查询前缀中添加“Android App sync:”。 这样,管理员就可以轻松识别出此操作并非真正的“紧急查询”,而只是为了执行同步而进行的辅助查询。

跟踪器数据写入

一般而言,处理数据的创建、编辑和删除有两种不同的情况:一种是对象可识别的情况(即它具有 uid 属性),另一种是对象不可识别的情况。

可识别对象(TrackedEntityInstance、Enrollment、Event)。这些存储库提供了一个 uid() 方法,可用于访问单个对象的编辑方法。如果该对象尚不存在,则必须先创建它。创建/编辑对象的典型工作流如下:

  • 使用 CreateProjection 类在存储库中添加一个新实例。
  • 保存此方法返回的uid。
  • 使用 uid() 方法并传入之前的 uid,即可调用编辑方法。

在代码中,它看起来像:

String eventUid = d2.eventModule().events().add(
    EventCreateProjection.create("enrollment", "program", "programStage", "orgUnit", "attCombo"));

d2.eventModule().events().uid(eventUid).setStatus(COMPLETED);

无法唯一标识的对象(TrackedEntityAttributeValue、TrackedEntityDataValue)。这些存储库具有一个 value() 方法,可让您访问针对单个对象的编辑方法。该方法接受的参数是能够唯一标识该值的参数。

例如,编写TrackedEntityDataValue就像:

d2.trackedEntityModule().trackedEntityDataValues().value(eventUid, dataElementid).set(“5”);

Image 类型的数据值在创建、更新或读取关联的文件资源时需要额外执行一步操作。更多详细信息请参见下文的 处理 FileResources 部分。

在只读 TEI 中写入事件{ #write-events-in-read-only-teis }

必须特别注意用户对 TEI、注册信息和事件的数据访问权限。 当执行任何 写入 方法时,SDK 会修改数据的状态,以便在下次同步时将其上传至服务器。如果用户对某个特定元素没有写入权限,应用程序应阻止对该元素的编辑操作。

该应用必须遵守的限制如下:

  • TrackedEntityInstances: 用户必须对 TrackedEntityType 拥有数据写入权限。
  • **注册:**用户必须对**TrackedEntityType和Program**都具有数据写入权限(此额外限制由SDK规定)。
  • 事件:**用户必须对 **ProgramStage 具有数据写入权限。

追踪器数据上传

TrackedEntityInstance和事件存储库具有upload()方法来分别上传Tracker数据和Event数据(无需注册)。如果通过过滤方法缩小了存储库范围,则将仅上载经过过滤的对象。

d2.( trackedEntityModule() | eventModule() )
    .[ filters ]
    .upload();

Data whose state is ERROR or WARNING cannot be uploaded. It is required to solve the conflicts before attempting a new upload: this means to do a modification in the problematic data, which forces their state back to TO_UPDATE.

从 2.37 版本开始,引入了一个新的追踪器导入器(/api/tracker 端点)。 默认的追踪器导入器仍是旧版(/api/trackedEntityInstances),但您可以通过 Android 设置 Web 应用选择启用此新追踪器导入器(参见 同步)。 这是 SDK 内部的变更;向应用公开的 API 保持不变。

跟踪器冲突

解析服务器响应,以确保数据已正确上传到服务器。如果服务器响应包含导入冲突,则这些冲突存储在数据库中,因此应用程序可以检查它们并采取措施解决它们。

d2.importModule().trackerImportConflicts()

成功上传对象后,与TrackedEntityInstance,注册或事件相关联的冲突会自动消除。

SDK尝试通过解析服务器响应来识别冲突dataElement或属性。如果是这样,它还会在发生冲突时存储元素的值,以便应用程序可以在尚未确定值的情况下突出显示元素。

跟踪器数据:保留值

配置为 唯一 和 自动生成 的受追踪实体属性,将由服务器根据用户定义的模式生成。这些值只能由服务器生成,这意味着我们需要预先保留这些值,以便在离线操作时能够使用它们。

该应用负责离线之前保留生成的值。这可以通过以下方式触发:

// Reserve values for all the unique and automatically generated trackedEntityAttributes.
d2.trackedEntityModule().reservedValueManager().downloadAllReservedValues(numValuesToFillUp)

// Reserve values for a particular trackedEntityAttribute.
d2.trackedEntityModule().reservedValueManager().downloadReservedValues("attributeUid", numValuesToFillUp)

根据应用程序预期脱机的时间长短,它可以决定要保留的值的数量。如果属性模式取决于组织单位代码,则SDK将为所有相关orgunits保留值。有关Javadoc中逻辑的更多详细信息。

保留值可以通过以下方式获得:

d2.trackedEntityModule().reservedValueManager().getValue("attributeUid", "orgunitUid")

跟踪器数据:关系

该 SDK 支持所有类型的关系。这些关系会在同步时下载,用户可以访问、创建或修改它们。

TEI Shallow copy { #shallow-copy } 合并策略(DISCARD 或 LAST_UPDATED)
TEI DISPLAYKEYVALUEPAIR DISPLAYKEYVALUEPAIR DISPLAYKEYVALUEPAIR
报名 DISPLAYKEYVALUEPAIR DISPLAYKEYVALUEPAIR DISPLAYKEYVALUEPAIR
活动 DISPLAYKEYVALUEPAIR DISPLAYKEYVALUEPAIR DISPLAYKEYVALUEPAIR
支持的关系

可通过“关系”模块访问各种关系。

查询与TEI相关的关系。

d2.relationshipModule().relationships().getByItem(
    RelationshipHelper.teiItem("trackedEntityInstanceUid")
)

查询与某次注册相关的关系。

d2.relationshipModule().relationships().getByItem(
    RelationshipHelper.enrollmentItem("enrollmentUid")
)

或者查询与某个事件相关的关系。

d2.relationshipModule().relationships().getByItem(
    RelationshipHelper.eventItem("eventUid")
)

在同一个模块中,您可以使用 RelationshipHelper 来建模任何类型的新关系,并随后将其添加到关系集合存储库中:

Relationship relationship = RelationshipHelper.teiToTeiRelationship("fromTEIUid", "toTEIUid", "relationshipTypeUid");

d2.relationshipModule().relationships().add(relationship);

If the related trackedEntityInstance does not exist yet and there are attribute values that must be inherited, you can use the following method to inherit attribute values from one TEI to another in the context of a certain program. Only those attribute marked as inherit will be inherited.

d2.trackedEntityModule().trackedEntityInstanceService()
    .inheritAttributes("fromTeiUid", "toTeiUid", "programUid");

要访问与 relationshipConstraint 相关的 dataElements 和 attributes,可以通过 trackerDataView 属性进行访问,如下例所示:

relationshipType.toConstraint().trackerDataView().attributes();
relationshipType.toConstraint().trackerDataView().dataElements();

汇总数据

汇总数据下载

重要提示

请参阅 设置应用 部分,了解如何使用该应用控制同步参数。

d2.aggregatedModule().data().download()

默认情况下,SDK 会下载与以下内容对应的 汇总数据值、数据集 完整注册值 和 批准信息:

  • 数据集:所有可用的数据集(即用户至少已读取过的 (数据访问)。
  • ** OrganisationUnits **:捕获范围。
  • 期间:所有可用期间,至少意味着:
  • 天数:过去60天。
  • 周:过去13周(包括开始日期的变体)。
  • 双周刊:最近13个双周刊。
  • 每月:过去12个月。
  • 双月刊:最近6个双月。
  • 宿舍:最近5个季度。
  • 六个月一次:最近5个六个月(从一月和四月开始)。
  • 每年:最近5年(包括财务年度变体)。

此外,如果有任何数据集允许在**未来期限**内输入数据, 该 SDK 将下载这些开放时段的数据并将其存储起来。

Sdk还会跟踪最新的成功下载,以便 避免下载未修改的服务器数据。

在下载**数据审批**时,除了 组织单位和时间段之外,还将考虑工作流和属性选项 的组合标识符。数据审批可能出现的 状态包括:

  • 无法批准。数据审批不适用于此选择。(数据 既不是*已批准*也不是*未批准*)。
  • UNAPPROVED_WAITING。可以批准此选择的数据,但是 在等待下级批准之前 已批准。
  • UNAPPROVED_ELSEWHERE。数据未获批准,正在等待 在其他地方获得批准(此处无法批准)。
  • UNAPPROVED_READY。数据未获批准,随时可以批准 用于此选项。
  • UNAPPROVED_ABOVE。上面的数据未获批准。
  • APPROVED_HERE。数据已获批准,且是在此处获批的(因此可能是 此处未批准)。
  • APPROVED_ELSEWHERE。数据已获批准,但未在此处获得批准(因此 (此处无法撤销批准)。)
  • APPROVED_ABOVE。数据已由上级批准。
  • ACCEPTED_HERE。数据已在此处获得批准并被接受(因此可能是 此处未批准)。
  • ACCEPTED_ELSEWHERE。数据被批准和接受,但其他地方。

数据批准仅针对大于2.29的版本下载。

汇总数据写入

句号

为了写入数据值或数据集完整的注册,必须提供一个期间ID。期间存储在数据库的表中,并且 提供的期间ID必须已经存在于该表中,否则,将引发外键错误。为了避免这种情况,PeriodHelper是 暴露在PeriodModule内部。在添加与数据集相关的聚合数据之前,必须调用以下方法:

Single<List<Period>> periods = d2.periodModule().periodHelper().getPeriodsForDataSet("dataSetUid");

这将确保: 1. 该应用程序将选择给定的时段之一,以防止出现格式错误或错误的时段。 2. The app will only be able to pick the future periods defined by the field DataSet.openFuturePeriods. 3. 该应用程序将只能选择基于“汇总数据下载”部分中声明的限制定义的过去时间段。

资料值

DataValueCollectionRepository 提供了一个 value() 方法,用于访问编辑方法。该方法接受的参数是能够唯一标识一个值的参数。

DataValueObjectRepository valueRepository = d2.dataValueModule().dataValues()
    .value("periodId", "orgunitId", "dataElementId", "categoryOptionComboId", "attributeOptionComboId");

valueRepository.set("value")

数据集完成注册

SDK在数据集模块内提供了一个收集存储库,用于 数据集完整注册。该存储库包含要添加的方法 新补全并将其删除。

要添加新的数据集,可以使用add()完成注册。 方法:

d2.dataSetModule().dataSetCompleteRegistrations()
    .add(dataSetCompleteRegistration);

为了将它们从数据库中删除,存储库提供了一个 value() 方法,该方法可访问删除方法(delete() 和 deleteIfExist())。该方法接受的参数是 能够唯一标识该数据集完整 注册信息的参数。

d2.dataSetModule().dataSetCompleteRegistrations()
    .value("periodId", "orgunitId", "dataSetUid","attributeOptionCombo")
    .delete()

汇总数据上传

DataValueCollectionRepository 提供了一个 upload() 方法,用于上传聚合后的数据值。

d2.dataValueModule().dataValues().upload();

数据集实例

SDK中的DataSetInstance是现有聚合数据的便捷表示。 DataSetInstance表示DataSet-Period-Orgunit-AttributeOptionCombo的唯一组合,并包含一些额外信息,例如同步状态,值计数或某些属性的displayName。

d2.dataSetModule().dataSetInstances()
    .[ filters ]
    .get()

// For example
d2.dataSetModule().dataSetInstances()
    .byDataSetUid().eq("datasetUid")
    .byOrganisationUnitUid().eq("orgunitUid")
    .byPeriod().in("201901", "201902")
    .get();

If you only need a high level overview of the aggregated data status, you can use the repository DataSetInstanceSummary. It accepts the same filters and returns a count of DataSetInstance for each combination.

处理FileResources

该 SDK 提供了一个模块(FileResourceModule)和两个辅助类(FileResourceDirectoryHelper 和 FileResizerHelper),用于处理文件。

在移动网络连接环境下,处理 fileResources 可能会消耗大量带宽。 因此,在下载数据时,fileResources 默认不会被下载,如需使用,必须显式下载。建议仅在确实需要在设备中保留这些文件时才下载 fileResources。若未下载,不会对数据完整性造成任何负面影响;唯一的影响是设备中无法使用这些文件。

另一方面,fileResource 上传并非可选操作:在上传数据时,SDK 会将设备中创建的所有 fileResource 一并上传。这一点对于确保同步成功和维护数据完整性至关重要。

文件资源模块

该模块包含下载与下载的数据关联的文件资源和数据库的文件资源收集存储库的方法。

  • 文件资源下载。 fileResourceDownloader() 提供了用于筛选我们想要下载的文件资源的方法。它会查找符合筛选条件且其文件资源此前尚未被下载的条目。
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()

该 SDK 的默认 maxContentLength 值为 6000000。

下载文件后,您可以获得通过存储库下载的不同文件资源。

  • 文件资源收集库。 通过此存储库,可以请求文件,保存新文件并将其上传到服务器。

  • 获取。其行为与任何其他 SDK 存储库类似。它允许根据需要应用不同的过滤条件来获取集合。

    d2.fileResourceModule().fileResources()
        .[ filters ]
        .get()
    
  • 添加。要保存文件,必须使用存储库的 add() 方法,并提供一个 File 类型的对象来添加该文件。 add() 方法将返回添加文件时生成的 uid。应使用该 uid 来更新与该文件资源关联的受追踪实体的属性值或数据值。

    d2.fileResourceModule().fileResources()
        .add(file); // Single<String> The fileResource uid
    

文件大小调整器帮助程序

Sdk提供了一个帮助调整图像文件大小的助手(FileResizerHelper)。该帮助器包含一个resizeFile()方法,该方法接受要缩小的文件以及缩小尺寸。

可能的尺寸在下表中。

小 中 大
256像素 512像素 1024像素

助手将获取文件,测量图像的高度和宽度,确定两侧的哪一个较大,并将最大的一侧减小到给定的尺寸,另一侧按比例缩放。 图像缩放将始终保持比例。

如果最后一张图像小于您要调整尺寸的尺寸,则将返回相同文件而无需修改。

resizeFile() 方法将返回一个新文件,该文件位于待调整大小文件的同一父目录下,文件名为 resized-DIMENSION- 加上未调整大小的文件名。

文件资源目录帮助程序

FileResourceDirectoryHelper 辅助类提供了两个方法。

  • getFileResourceDirectory()。该方法返回一个 File 对象,其路径指向 sdk_resources 目录,SDK 将把与文件资源相关的文件保存到该目录中。

  • getFileCacheResourceDirectory(). This method returns a File object whose path points to the sdk_cache_resources directory. This should be the place where volatile files are stored, such as camera photos or images to be resized. Since the directory is contained in the cache directory, Android may auto-delete the files in the cache directory once the system is about to run out of memory. Third party applications can also delete files from the cache directory. Even the user can manually clear the cache from Settings. However, the fact that the cache can be cleared in the methods explained above should not mean that the cache will automatically get cleared; therefore, the cache will need to be tidied up from time to time proactively.