Step 7: Build the screening home and Questionnaire flow
What we are building
A patient-facing home with two independent cards: one for FINDRISC and one for blood glucose. The Questionnaire card launches the approved FHIR resource through QuestionnaireForm; the glucose card remains optional for screening completion.
What you should already have
- Authentication and the protected Patient area from step 6.
- A published, reviewed Questionnaire with matching URL, version, ID, linkIds, and answerOption ordering.
- The scoring function from step 5.
The implementation
Load the approved Questionnaire and latest assessment
Create app/(patient)/assessment.tsx:
import { usePatientQuestionnaires } from "@ovok/core";
import { QuestionnaireForm } from "@ovok/native/questionnaire-response-form";
import { useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { ovokConfig } from "../../src/config/ovok";
import { scoreFindrisc } from "../../src/features/findrisc/scoring";
export default function AssessmentScreen() {
const [notice, setNotice] = useState<string>();
const [result, setResult] = useState<ReturnType<typeof scoreFindrisc>>();
const {
data: questionnaires,
loading,
error,
reload,
} = usePatientQuestionnaires({ _id: ovokConfig.questionnaireId });
const questionnaire = questionnaires?.find(
(item) => item.id === ovokConfig.questionnaireId,
);
if (loading) return <ActivityIndicator />;
if (error) {
return <Text onPress={reload}>Questionnaire unavailable. Tap to retry.</Text>;
}
if (!questionnaire) {
return <Text>The approved screening form is not available in this project.</Text>;
}
if (
questionnaire.url !== ovokConfig.questionnaireUrl ||
questionnaire.version !== ovokConfig.questionnaireVersion
) {
return <Text>The screening form needs review before it can be used.</Text>;
}
return (
<View>
<QuestionnaireForm
key={`${questionnaire.id}:${questionnaire.version}`}
questionnaire={questionnaire}
locale="en"
onSuccess={(response) => {
try {
setResult(
scoreFindrisc(questionnaire, response, {
url: ovokConfig.questionnaireUrl,
version: ovokConfig.questionnaireVersion,
}),
);
setNotice("Assessment saved.");
} catch {
setResult(undefined);
setNotice("The saved form needs review before a score can be shown.");
}
}}
onError={() => setNotice("The assessment could not be saved. Try again.")}
>
<QuestionnaireForm.Header>
<QuestionnaireForm.Header.Title />
<QuestionnaireForm.Header.Subtitle />
</QuestionnaireForm.Header>
<QuestionnaireForm.Content>
<QuestionnaireForm.Content.Item />
<QuestionnaireForm.Content.Error />
</QuestionnaireForm.Content>
<QuestionnaireForm.Navigation>
<QuestionnaireForm.Navigation.PreviousButton />
<QuestionnaireForm.Navigation.NextButton />
<QuestionnaireForm.Navigation.SubmitButton />
</QuestionnaireForm.Navigation>
</QuestionnaireForm>
{notice ? <Text accessibilityRole="alert">{notice}</Text> : null}
{result ? (
<Text>
FINDRISC score: {result.total}. Category: {result.category}.
</Text>
) : null}
</View>
);
}
QuestionnaireForm supports paged sections, required validation, previous/next navigation, and SDK-managed QuestionnaireResponse creation. In this mode, the SDK associates the response with the active Patient and saves it. The callback fires after the save. Read the QuestionnaireResponseForm guide for rendering and submission behavior.
The current form persists the completed response; do not promise that an unfinished form resumes after the app is closed. If cross-session drafts are a product requirement, design and verify a separate FHIR draft workflow before adding it.
Compose the independent home cards
Create app/(patient)/index.tsx:
import {
useCurrentPatientProfile,
usePatientQuestionnaireResponses,
} from "@ovok/core";
import { Link } from "expo-router";
import { Text, View } from "react-native";
import { ovokConfig } from "../../src/config/ovok";
export default function ScreeningHome() {
const { data: current } = useCurrentPatientProfile();
const patientId = current?.profile.id;
const { data: responses, loading } = usePatientQuestionnaireResponses({
...(patientId ? { patient: `Patient/${patientId}` } : {}),
questionnaire: `Questionnaire/${ovokConfig.questionnaireId}`,
_sort: "-authored",
_count: 1,
});
const latest = responses?.[0];
return (
<View>
<Text>Diabetes risk screening</Text>
<Link href="/(patient)/assessment">
<View>
<Text>Check your Type 2 diabetes risk</Text>
<Text>
{loading
? "Loading assessment status"
: latest?.status === "completed"
? "Completed"
: "Not started"}
</Text>
</View>
</Link>
<Link href="/(patient)/devices">
<View>
<Text>Blood glucose</Text>
<Text>Connect a supported meter or view recent readings.</Text>
</View>
</Link>
</View>
);
}
Use CMS strings for the labels in the final UI. The two cards intentionally have no dependency: a Patient may complete FINDRISC without pairing a meter.
Important Ovok decisions
- Keep the questionnaire’s exact approved content and translations in its FHIR resource, not in screen code or CMS UI groups.
- SDK-managed submission creates a completed QuestionnaireResponse. The response is stored before the score is rendered.
- A QuestionnaireResponse without a matching reviewed canonical URL and version is saved data, but this app does not display a score for it.
$populatemay prefill only fields supported by the approved Questionnaire and correctly sourced Patient data. It must not fill lifestyle answers or family history.$extractis optional and never calculates this risk score.
Expected result
The home screen clearly shows an assessment entry point and a separate glucose entry point. A Patient can navigate the approved FHIR form, answer required questions, save a QuestionnaireResponse, and see the reviewed app-owned score.
Common errors and troubleshooting
- Questionnaire is hidden: check the project ID, Patient read permission, and Questionnaire status/access.
- Submit succeeds but no score appears: inspect the saved response’s Questionnaire reference and compare its linkIds and answer options with the reviewed map.
- The form reloads without the previous draft: the current managed form flow does not promise cross-session draft recovery.
- The app forces glucose before the questionnaire: remove the dependency; the two cards represent separate data sources.
- An unsupported answer type renders incorrectly: check the current Questionnaire form renderer support and keep the Questionnaire within supported FHIR answer types.
Previous / Next
Previous: initialise Ovok, auth, and Bluetooth · Next: connect TeleBGM and save readings