Playing a webview campaign list with the native player

    Playing a webview campaign list with the native player


    Article summary

    When integrating a hybrid app, you do not need to rebuild the campaign list natively. Keep the plugin list rendered inside the webview, and hand off to native playback only when a campaign is tapped.

    Only campaignKey is passed from the webview campaign list to native, which opens the full player

    Comparing the two approaches

    Build the list natively as well

    You implement the list UI natively and open a preview screen per item. Composing the list, paging, and refreshing state are all yours to manage.

    Reuse the webview list (this guide)

    The plugin's setOverall keeps rendering the list inside the webview. Only the key of the tapped campaign is passed to native, and playback is handled by play().

    The webview and native are separate layers. Native already holds the accessKey, so the only value passed from the webview to native is campaignKey.

    Flow

    1. Webview — renders the list and passes campaignKey to native on tap
    2. Native — receives the value
    3. Native — plays it with the SDK

    Step 1. Render the list and pass the key to native on tap

    setOverall renders the list. When no native interface exists, the click callback falls back to the default web modal player, so the same page is safe to ship both on the web and inside a hybrid app.

    On iOS, inline playback requires allowsInlineMediaPlayback = true in WKWebViewConfiguration. (This is a webview setting, independent of the code below.)

    JavaScript

    <script src="https://static.shoplive.cloud/shoplive.js"></script>
    <script>
      var accessKey = 'YOUR_ACCESS_KEY';
    
      var messageCallback = {
        ON_CLICK_CAMPAIGN_LIST_ITEM: function (payload) {
          var APP_INTERFACE = "ShopLiveAppInterface";
          var campaignKey = payload.campaignKey;
    
          if (window[APP_INTERFACE]) {
            // Android, Flutter - both support postMessage(String) only
            window[APP_INTERFACE].postMessage(campaignKey);
          } else if (window.webkit?.messageHandlers?.[APP_INTERFACE]) {
            // iOS
            window.webkit.messageHandlers[APP_INTERFACE].postMessage(campaignKey);
          } else {
            // No native interface (plain web) - fall back to the web modal
            cloud.shoplive.showFeaturedPlayerModal({ campaignKey: campaignKey });
          }
        }
      };
    
      cloud.shoplive.init({ accessKey: accessKey, messageCallback: messageCallback });
    </script>
    
    <div id="shoplive-overall"></div>
    <script defer>
      cloud.shoplive.setOverall('shoplive-overall');
    </script>

    Step 2. Receive the value on native

    Register the interface name used above (ShopLiveAppInterface) on the native side. This registration is not a Shoplive SDK feature — it is the standard JS-to-native bridge each webview provides. Only the campaignKey string is exchanged, so all three platforms receive it as-is with no parsing.

    On iOS, userContentController.add() must be called before the WKWebView is created, that is, before building the webview with this configuration.

    Kotlin · Android

    webView.addJavascriptInterface(ShopLiveAppInterface(activity), "ShopLiveAppInterface")
    
    class ShopLiveAppInterface(val activity: Activity) {
        @JavascriptInterface
        fun postMessage(campaignKey: String) {
            activity.runOnUiThread {
                /* Step 3: call ShopLive.play() */
            }
        }
    }

    Swift · iOS

    configuration.userContentController.add(self, name: "ShopLiveAppInterface")
    
    func userContentController(_ controller: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        guard message.name == "ShopLiveAppInterface",
              let campaignKey = message.body as? String else { return }
    
        // Step 3: call ShopLive.play()
    }

    Dart · Flutter

    controller.addJavaScriptChannel(
      'ShopLiveAppInterface',
      onMessageReceived: (JavaScriptMessage message) {
        final campaignKey = message.message;
    
        // Step 3: call ShopLive.play()
      },
    );

    Any JavaScript running inside the webview can call this bridge. If the webview can navigate to URLs other than your own page (product links, for example), those pages can call ShopLiveAppInterface too. We recommend opening external destinations in a new window or the external browser so that only your page stays inside the webview.

    Step 3. Play on native

    The single campaignKey received through the bridge is enough for all three platforms to open the full player in the same shape. (Guest playback, assuming the native SDK is already initialized with the accessKey — Android ShopLive.setAccessKey, iOS ShopLive.configure.)

    Kotlin · Android

    ShopLive.play(activity, ShopLivePlayerData(campaignKey))

    Swift · iOS

    ShopLive.play(data: .init(campaignKey: campaignKey))

    Dart · Flutter

    shopLivePlayer.play(data: ShopLivePlayerData(campaignKey: campaignKey));

    Playing as a logged-in user (optional)

    Skip this step for guest playback. Two things need to be decided — which authentication method to use, and where the value comes from.

    Authentication method

    A · Advanced (JWT) authentication

    Verified with a JWT signed by your server. setAuthToken

    B · Simple authentication

    Passes only userId, with no signature or server-side verification. setUser

    A · Advanced authentication (JWT)

    Kotlin · Android

    ShopLive.setAuthToken(jwt) // before play()

    Swift · iOS

    ShopLive.authToken = jwt

    Dart · Flutter

    shopLiveCommon.setAuthToken(userJWT: jwt);

    The JWT is signed by your own server using the secret key issued by Shoplive. Issue it for the same user as the JWT used by the webview plugin.

    B · Simple authentication (userId)

    Kotlin · Android

    ShopLive.setUser(ShopLiveCommonUser(userId))

    Swift · iOS

    ShopLiveCommon.setUser(user: ShopLiveCommonUser(userId: userId))

    Dart · Flutter

    shopLiveCommon.setUser(user: ShopLiveCommonUser(userId: userId));

    Where the value comes from

    Where you obtain the authentication value before calling play() depends on how login is structured in your hybrid app.

    A · Native manages login

    The native app already holds the value. Leave the bridge payload as is and simply call the API above with that value right before play().

    B · Login lives only in the webview session

    Native does not know the user. Send the value together in the Step 1 bridge payload, and have native call the API above with it.

    For case B a single string is not enough: change the Step 1 code to send an object carrying the value, and change Step 2 to parse JSON/an object instead of a string. (The example below uses JWT.)

    JavaScript · extending the Step 1 code

    var bridgePayload = { campaignKey: campaignKey, authToken: currentUserJWT };
    
    if (window[APP_INTERFACE]) {
      window[APP_INTERFACE].postMessage(JSON.stringify(bridgePayload));
    } else if (window.webkit?.messageHandlers?.[APP_INTERFACE]) {
      window.webkit.messageHandlers[APP_INTERFACE].postMessage(bridgePayload);
    }