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.

    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.

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 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.
    })