Virtual and Phone Visit
The VirtualService
provides the ability to set up a virtual video call with a Provider, where a patient can get nearly instantaneous health care. The video call is handled entirely by the SDK. That includes a virtual waiting room, as well as the video conference itself.
For phone visits, the same service is used, but with a phone visit flag. In that scenario, the SDK is used to queue up the phone visit, but no UI is presented.
Practice Service
The SDK’s PracticeService and VirtualService are closely related. Please see that guide first, as it is the starting point for scheduling a Virtual Visit.
Determining Visit State
One crucial piece of information required for Virtua or Phone Visits is the visit state - the U.S. State in which care is being requested.
This is important because in most cases, health care providers only have licenses for individual states, and providing care outside their licensed state(s) would be illegal.
The first step of the virtual scheduling flow in your application should be to ask the user which state they’re requesting care in.
A list of regions supported by a Virtual Practice can be determined based on the results of the PracticeService.getVirtualPractice
SDK method.
The method returns a list of VirtualPracticeRegions which can be displayed to the user for them to select one.
In addition to the regions list returned by PracticeService.getVirtualPractice
, it may be wise to add an Other
option, which, when selected, will inform the user that they are currently unable to be supported by your healthcare system’s providers.
After the user selects the visit state, you will need to save their selection for later use in the scheduling flow.
Creating the Patient
Now that we have determined the state in which care is being requested, we next have to ensure the patient’s demographics have been created and exist in the required EHR system.
Note: If a patients demographic is known to exist in the EHR System, these calls to findOrCreate(Dependent)Patient
are NOT required. However you must have a full DexcarePatient
or EHRPatient
object in order to book a virtual visit.
For more information on how to create patients - visit the Patient Service section
Payment Method
One of the required parameters for scheduling a virtual visit is the patient’s payment/billing information.
Please see the Payment Method guide for more detail on how to collect the patient’s payment information.
Push Notifications
You will also want to register the user’s device for push notifications. As of SDK 5.0, push notifications are optional for both iOS and Android. However, it is still HIGHLY recommended to set up push notifications in order to have a better patient experience.
The steps to register a device for push notifications are different between iOS and Android:
-
You will only need to do this setup once for your project.
Before push notifications will work in your app, you’ll need to do some setup, and then provide the DexCare team with a couple config values.
Android
For setup on Android, you’ll need to create a Firebase project and implement Firebase in your app. Please follow this Firebase guide for instructions on how to do that.
Once setup, the config value needed from your project is the Cloud Messaging
Server key
. This can be found under the Project Settings, in the Cloud Messaging tab. This needs to be securely transferred to your DexCare contact, and we’ll do the necessary setup on the DexCare backend.iOS
For iOS, you’ll need to securely send your APNS Certificate to your DexCare contact. As these certificates expire after 2 years, you’ll need to repeat these steps every few years.
-
Push notifications are currently supported by the Virtual Visit system only. Retail and other systems do not use the push notification service.
- To start, make sure your entitlements for your project has the push notification entitlement
- When you set up the DexcareSDK, you must pass in a
pushNotificationAppId
and apushNotificationPlatform
inside of theVirtualSDKConfiguration
in order for the Virtual Visit to request Push Notification Access from the device. ThepushNotificationAppId
will be given to you by your Dexcare contact. - The DexcareSDK will automatically prompt for push notification access when the user first visits the waiting room. Users will be allowed into the waiting if they have denied access to notifications.
Inside your
AppDelegate
on your project, you must implementdidRegisterForRemoteNotificationsWithDeviceToken
in order to receive a device token. This function will be called when the app has successfully registered with Apple Push Notification service. This token must be passed up the SDK in order for the device to receive notifications.// In AppDelegate/SceneDelegate func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { DexcareSDK.virtual.updatePushNotificationDeviceToken(deviceToken) }
If the device fails to get a notification (because of permission for example), implement
didFailToRegisterForRemoteNotificationsWithError
to see why. Most likely the user has denied, or the provisioning profile to build the app hasn’t been set up correctly.From there, push notifications work the same as any regular Apple Push Notification. Currently Dexcare only sends basic push notifications and must be handled by you when in the app if you wish to show the notification.
-
Intro and setup
Push notifications are currently only used in Virtual Visits. Retail and other services do not use push notifications.
You will need create your own Firebase project and implement the Firebase SDK.
One of the required parameters to start a virtual visit is a
RegisterPushNotification
object. It takes two parameters:- appId: a string in the format
[stage|prod].FCM.[brand].HealthConnect
. - fcmToken: a token retrieved from the Firebase SDK, representing the user’s device.
appId
Pseudo-code:
val environment = if (isReleaseBuild) "prod" else "stage" val brand = getString(R.string.brand_name) // brand will be provided by your DexCare contact val appId = "$environment.FCM.$brand.HealthConnect" // getToken() should retrieve the token as cached from the above step val registerPushNotification = RegisterPushNotification(appId, getToken())
fcmToken
FirebaseMessaging.getInstance().token .addOnCompleteListener { task -> if (!task.isSuccessful) { // retrieval failed, Firebase may be set up incorrectly return@OnCompleteListener } // You will need to cache the token somewhere in order to access it later when constructing the RegisterPushNotification // saveToken(task.result?.token) }
The
RegisterPushNotification
can then be passed in to the VirtualService.startVirtualVisit SDK API.Handling the push notifications
You will need to set up a service in your app to listen for push notifications and handle displaying them.
See the Firebase Cloud Messaging documentation for more information on how to set up the service.
A sample service can be found on the SDK Sample App here.
The DexCare push notifications come in as
data
messages, meaning your app needs to handle the displaying of the notification - it is not automatic.Below are two samples of the information provided in the data payload:
Alert Payload
messageType=1, text=Don't forget about your virtual visit! Return to Waiting Room., alert=Don't forget about your virtual visit! Return to Waiting Room., sound=default, title=Express Care Virtual Visit, message=Don't forget about your virtual visit! Return to Waiting Room., visitURL=https://uat.example.com/visit/71b3d8a1-****-****-****-a5b2d439cb47
Chat message payload
messageType=3, text=Your provider has sent you a chat message. View message., alert=Your provider has sent you a chat message. View message., sound=default, title=Express Care Virtual Visit, message=Your provider has sent you a chat message. View message., visitURL=https://uat.example.com/visit/71b3d8a1-****-****-****-a5b2d439cb47}
Each of the properties shown above are accessible through the
RemoteMessage.data
map.The
visitURL
will change based on your environment, and can be parsed to retrieve the Id of the visit.Message Types
There are four different message types that can be sent at different times during a Virtual Visit.
0
- An alert from a PRR (Patient Registration Representative)1
- An alert from a Provider2
- A chat message from a PRR (Patient Registration Representative)3
- A chat message from a Provider
Conclusion
Ultimately, what you choose to do with the provided payload information is up to you. However, it is highly recommended to display the push notifications when the app is backgrounded. Doing so helps ensure that the patient will not miss their virtual visit, nor waste time of the provider.
- appId: a string in the format
VirtualVisitDetails
The last step before scheduling the visit is creating the VirtualVisitDetails
object. This object will contain the remaining bits and pieces of information required to schedule the visit.
The VirtualVisitDetails
has several properties:
Property | Comment |
---|---|
visitReason | A short description describing the reason for scheduling the virtual visit |
patientDeclaration | An enum used to determine who the visit should be scheduled for |
acceptedTerms | Whether or not the user has accepted the Terms and Conditions required to schedule a virtual visit |
userEmail | This should always be a non-empty email address which can be used to contact the app user. Note: the patient email address as returned by Epic is not guaranteed to be present. For this reason, it is recommended to always collect this information from an alternative source, e.g. Auth0 email. |
contactPhoneNumber | A phone number that can be used to communicate with the patient. |
preTriageTags | An optional list of tags to be used in analytics when scheduling the visit. |
actorRelationshipToPatient | optional. When booking for a dependent, this must be filled in. It is not required when booking for the logged in patient. |
stateLicensure | two character state code |
visitTypeName | virtual or phone |
practiceId | optional - default practice used if not passed in |
brand | optional - default brand used if not passed in |
assessmentToolUsed | optional - what preassessment tool used |
interpreterLanguage | optional - language to request if interpreter services are available |
assignmentQualifiers | optional - array - adult , pediatric |
urgency | optional - 0 default, otherwise “high priority” |
homeMarket | optional - used to filter visits in more detail beside state |
initialStatus | optional - used to set an initial status of a vist |
Creating the Visit
Now that we’ve collected all of the required information, the Virtual or Phone Visit can now be scheduled.
-
let dexcareSDK = DexcareSDK(configuration: ...) dexcareSDK.virtualService.createVirtualVisit( presentingViewController: self.navigationController, dexcarePatient: patient, // a full `DexcarePatient` or `EHRPatient` object, including the demographics associated to an EHR System for the patient requesting the visit. If this is for a dependent, this must be a dependent patient. If using `EHRPatient` use the overloaded method with an `EHRPatient` parameter virtualVisitDetails: VirtualVisitDetails( acceptedTerms: true, assignmentQualifiers: nil, // array of `VirtualVisitAssignmentQualifier`. Supported values can be loaded via `VirtualService.getAssignmentQualifiers` patientDeclaration: .self, stateLicensure: "AK", // which state the visit is being booked into. visitReason: "DexcareSDKTestApp test visit", visitTypeName: .virtual, userEmail: "contact@email.com", contactPhoneNumber: "(204)233-2332", practiceId: "12345678", // which practice assessmentToolUsed: nil, brand: nil, // if multiple brands supported interpreterLanguage: nil, preTriageTags: nil, urgency: nil, actorRelationshipToPatient: nil), paymentMethod: .couponCode("dexcare"), // What `PaymentMethod` enum is used actor: nil, // When booking for a dependent, set this to a full `DexcarePatient` or `EhRPatient` object of the logged in User. Set to nil when booking a visit for the logged in user. onCompletion: { reason in // Virtual visit completed. This could also be a provider closing the visit. Please check the `VisitCompletionReason` enum for types of reasons. // If the request was a phone visit - this will call after successfull creation with a reason of `VisitCompletionReason.phoneVisit`. NOTE: THIS DOES NOT MEAN THE VISIT IS FINISHED. }, success: { visitId in // visit is started as the SDK wills how the waitingRoom UI. Save this Visit ID so that if disconnect you can resume a Visit if need be. }, failure: { error in // handle the error } )
-
DexCareSDK.virtualService .createVirtualVisitWithPatientActor( activity, // or a Fragment patient, VirtualVisitDetails( acceptedTerms = true, // patient has accepted terms of service assignmentQualifiers = null, // qualifications to assign visit to a provider - DefaultVirtualVisitAssignmentQualifiers.Adult.qualifier patientDeclaration = PatientDeclaration.Self, // is this visit being submitted by the patient or by a proxy stateLicensure = "stateLicensure", // state licensure required for provider to see patient eg. WA visitReason = "reasonForVisit", visitTypeName = DefaultVirtualVisitTypes.Virtual.type, // Phone or Virtual types practiceId = "practice_id", assessmentToolUsed = "ada", // if patient has done preassessment, which tool was used brand = "brand", interpreterLanguage = "en", // optional language requested if interpreter services are available; ISO 639-3 Individual Language codes userEmail = "contact@email.com", contactPhoneNumber = "(204)233-2332", preTriageTags = listOf("preTriageTag"), // list of scheduledDepartments urgency = null, // 0 for default urgency initialStatus = DefaultVisitStatus.Requested.status,// requested, waitoffline, actorRelationshipToPatient = null, // Refer to RelationshipToPatient for the full list ), paymentMethod, virtualActor = null, // individual acting as a Patient proxy, a full `DexcarePatient` or `EhRPatient` registerPushNotification = null ) .subscribe({ visitTypeNameIdIntentTriple -> val visitTypeName = visitTypeNameIdIntentTriple.first // phone or virtual val visitId = visitTypeNameIdIntentTriple.second val virtualVisitIntent = visitTypeNameIdIntentTriple.third (requireActivity() as MainActivity).activityResultLauncher.launch(virtualVisitIntent) requireActivity().startActivityForResult( virtualVisitIntent, MainActivity.VIRTUAL_REQUEST_CODE ) }, { // handle the error, show an error dialog, log the error, etc. })
Resuming a Visit
If for some reason the visit is disconnected, the visitId
returned from the createVirtualVisit
method can be used to reconnect to an existing virtual visit. Once a Virtual Visit is closed officially by the provider or by the client, a visitId
is no longer active and can not be used to resume.
If the visit is no longer active, a failure/error will be returned to your application.
-
let dexcareSDK = DexcareSDK(configuration: ...) dexcareSDK.virtualService.resumeVirtualVisit( visitId: lastVisitId, presentingViewController: self.navigationController, onCompletion: { reason in // Virtual visit completed. This could also be a provider closing the visit. Please check the `VisitCompletionReason` enum for types of reasons. }, success: { in // Virtual visit has resumed. Keep the original Visit Id again for any future resumes }, failure: { failedReason in // handle the error of `VirtualVisitFailedReason` // as an example `VirtualVisitFailedReason.expired` will let you know the visit Id is no longer valid // or `.virtualVisitNotFound` will tell you that the visit id is incorrect. } )
-
DexCareSDK.virtualService.resumeVirtualVisit( visitId, activity,// or fragment registerPushNotification, dexCarePatient ).subscribe({ intent -> // Start the waiting room. Your app should then listen for the result code returned using the request code. // VIRTUAL_VISIT_REQUEST_CODE should be a unique number to represent this activity. startActivityForResult(intent, VIRTUAL_VISIT_REQUEST_CODE) }, { error -> // handle the error, show an error dialog, log the error, etc. })
IdleTimer
When you’re in a Virtual Visit, the last thing you want is for your client’s screen to lock because they are idle and not touching the screen. On Android this is automatically done for you when you are in the virtual visit, on iOS, you must turn on and off the idleTimer before starting a virtual visit.
-
// Sets the idleTimer disabled so that the iOS Device screen does not lock. // Note: Having disabled the idle timer, the device will always be open and therefore take more battery power. It is recommended to turn it to
false
once the virtual visit is completeUIApplication.shared.isIdleTimerDisabled = true
Posting Feedback
After a Virtual Visit has completed successfully, you have the option to send feedback about this visit.
Note: The SDK will keep track of the last visitId as well as the patientId after a startVirtualVisit
or a resumeVirtualVisit
. This is kept in session only. If the SDK is reinitialized, these properties are removed and you will not be able to call postFeedback
.
VirtualFeedback
VirtualFeedback
is an enum that allows you to create any type of feedback you wish. It is broken up into 3 types
rating
- a question text field and an integer for a rating valuefeedback
- a question text field and a string answer valuefollowUp
- a question text and a boolean answer
You may send 1 to many
different VirtualFeedback
items in the call. It is up to you to create those.
The SDK has set up some default question strings, but you may customize this text if desired.
-
Anywhere you pass in
nil
for a question property it will use the default value set by the SDK (see example below)let dexcareSDK = DexcareSDK(configuration: ...) dexcareSDK.virtualService.postFeedback( feedbacks: [ .feedback(question: "Feedback question", answer: "feeback answer"), .rating(question: "What is your rating", rating: 1), .followUp(question:nil, answer: true) // with nil - the SDK will automatically use the VirtualFeedback.defaultQuestionString.followUp string when posting ], success: { in // completed successfully }, failure: { _ in // failed! } )
-
DexCareSDK.virtualService.postFeedback( listOf( VirtualFeedback.RateYourExperience(10), VirtualFeedback.HowWeDid("It went really smoothly and there weren't any connection issues."), VirtualFeedback.FollowUpOnExperience(true, "May we contact you for further feedback about this visit?") // overriding the default question text ) ).subscribe({ // feedback posted successfully }, { // handle the error, show an error dialog, log the error, etc. })