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

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

    1. To start, make sure your entitlements for your project has the push notification entitlement
    2. When you set up the DexcareSDK, you must pass in a pushNotificationAppId and a pushNotificationPlatform inside of the VirtualSDKConfiguration in order for the Virtual Visit to request Push Notification Access from the device. The pushNotificationAppId will be given to you by your Dexcare contact.
    3. 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 implement didRegisterForRemoteNotificationsWithDeviceToken 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 Provider
    • 2 - 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.

VirtualVisitInformation

The last step before scheduling the visit is creating the VirtualVisitInformation object. This object will contain the remaining bits and pieces of information required to schedule the visit.

The VirtualVisitInformation has several parameters:

  • 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.
  • currentState The state code in which the user is requesting care in, e.g. “WA”.
  • 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.
  • acceptedTerms Whether or not the user has accepted the Terms and Conditions required to schedule a virtual visit.
  • 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.
  • practiceRegionId - Required for non-deprecated scheduling methods. See the PracticeService guide for details.

Scheduling the Visit

Now that we’ve collected all of the required information, the Virtual Visit can now be scheduled.

  • let dexcareSDK = DexcareSDK(configuration: ...)
    
    dexcareSDK.virtualService.startVirtualVisit(
        presentingViewController: self.navigationController,
        paymentMethod: .`self`,
        virtualVisitInformation: VirtualVisitInformation(
            visitReason: "DexcareSDKTestApp test visit",
            patientDeclaration: .self,
            currentState: "AK",
            acceptedTerms: true,
            userEmail: "email@ok.com",
            contactPhoneNumber: "(204)233-2332",
            preTriageTags: [],
            actorRelationshipToPatient: nil, // set to a value when booking for a dependent
            practiceRegionId: practiceRegionId
        ),
        catchmentArea: catchmentArea, // retrieved via `PatientService.getCatchmentArea` or created manually
        patientDexCarePatient: patientDexCarePatient, // a full `DexcarePatient` 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
        actorDexCarePatient: nil, // When booking for a dependent virtual visit, set this to a full `DexcarePatient` object of the logged in User. Set to nil when booking a visit for the logged in user.
        practiceId: practiceId
        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: { 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
            .startVirtualVisit(
                activity, // or a Fragment
                registerPushNotification,
                paymentMethod,
                VirtualVisitInformation(
                    visitReason,
                    PatientDeclaration.Self, // Use PatientDeclaration.Other when booking for a dependent patient
                    region.regionId, // where the user is asking for care in
                    patientEmail,
                    patientContactPhoneNumber,
                    actorRelationshipToPatient = null, // Defaults to null, needs to be non-null when booking for a dependent
                    practiceRegionId
                ),
                catchmentArea, // retrieved via `PatientService.getCatchmentArea` or created manually
                patientDexCarePatient, // a full `DexcarePatient` 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
                actorDexCarePatient = null, // When booking for a dependent virtual visit, set this to a full `DexcarePatient` object of the logged in User. Set to null when booking a visit for the logged in user.
                practiceId
            )
            .subscribe({ visitIdIntentPair ->
                val visitId = visitIdIntentPair.first
                val virtualVisitIntent = visitIdIntentPair.second
                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 startVirtualVisit 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,
            registerPushNotification
        ).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 complete

    UIApplication.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 value
  • feedback - a question text field and a string answer value
  • followUp - 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.
    })