Roku with You.i Roku Cloud apps are almost inactive while the Roku client is playing a video, or when the end user isn’t using the Roku remote. During this time, Cloud server instances can be made available to other Roku clients by saving their state, which can be used by the new server instances to resume the application. This allows a greater number of users to be serviced by the Cloud infrastructure while reducing costs and the number of server instances running at a given time. We call this feature “Disconnect on Inactivity”.
To support this feature in your app, you need to add app logic to save and restore the app’s state at the appropriate times.
When a Roku client is inactive for more than the noActivitySessionTimeoutSec
time, the You.i Roku Cloud infrastructure disconnects the Roku client from the Cloud server by providing it with the saved state information from the previous connection.
The client reconnects again with the server after the end-user’s interaction with the app.
The Cloud module’s screenSaverWillStart
event, invoked 30 seconds before the screensaver starts, triggers the You.i Roku Cloud app to store its state on the Roku client.
The You.i Roku Cloud server saves its state with the Cloud.saveInstanceState(savedState)
API for a You.i React Native app, in response to the screenSaverWillStart
event.
The Roku server instance is destroyed once the saveInstanceState
is complete.
The saved state is a deep link to the screen at the point when the client became inactive. A stack of screen data could also be stored to restore the history. The app state is stored by the Roku client as a JSON stringified blob.
The following sequence diagram illustrates the sequence of events between the Roku client, Cloud infrastructure, and Cloud server when the screensaver is active for longer than noActivitySessionTimeoutSec
.
The following sequence diagram illustrates the sequence of events between the Roku client, Cloud infrastructure, and Cloud server when the screensaver is active for less than noActivitySessionTimeoutSec
, but the server exceeds two ping durations (which is every 10 seconds) from the client.
The following diagram explains the You.i Roku Cloud server activity cycle for Disconnect on Inactivity:
To enable the Disconnect on Inactivity feature, you need to update the client configuration file and set up a persistent key for the app state data.
Ensure that the state of the RN application does not include the video player state, since the Roku video player is used for playback.
Add the following to clientConfig.json
, located at ../youi/build/<platform>/<configuration>/assets/json/default/
:
{
"inactivityDisconnect": true
}
Setting inactivityDisconnect
to true
causes the application to save its state when the client is inactive.
The client sends the screenSaverWillStart
event 30 seconds before the screensaver actually starts, which enables the server to send the app state data to the client.
If the screensaver active time is longer than noActivitySessionTimeoutSec
, which is configured by the Cloud infrastructure, the connection to the server instance is terminated.
On an end-user’s interaction with the app, the connection between client and new server instance is established, and the client starts from the saved state, instead of the beginning.
When running this feature locally, the server terminates when disconnect on inactivity occurs. You must restart the server manually again.
The steps for setting up a persistent key for the app state data depend on whether you’re using React Navigation or React Redux.
This is an example from RNSampleApp, where NavigateState
is specified as the persistent key for the app state.
bool App::UserInit()
{
// ...
#if YI_CLOUD_SERVER
GetReactNativeViewController().AddModule<CloudConfig>();
#endif
// ...
}
bool App::UserStart()
{
bool isOK = PlatformApp::UserStart();
#if YI_CLOUD_SERVER
CYICloud::GetInterface().SetNavigationPersistenceKey("NavigationState");
#endif
return isOK;
}
// up to react-navigation version 3.x
class YiReactApp extends React.Component {
render() {
return <RootStack persistenceKey={"NavigationState"}/>;
}
}
// for react navigation version 4.x or above
class App extends PureComponent {
navigationRef = React.createRef();
constructor(props) {
super(props);
cloudEventEmitter.addListener('clientBackgrounded', this._saveInstanceState);
cloudEventEmitter.addListener('screenSaverWillStart', this._saveInstanceState);
}
_saveInstanceState = () => {
Cloud.saveInstanceState({ navigation: this.navigationRef.current.state });
};
_onLoadNavigationState = () => {
return Cloud.savedInstanceState && Cloud.savedInstanceState.length > 0 && Cloud.savedInstanceState[0].navigation;
};
render() {
return <NavigationContainer ref={this.navigationRef} loadNavigationState={this._onLoadNavigationState} />;
}
}
Redux-based RN apps need to register to receive the Cloud module’s clientBackgrounded
or screenSaverWillStart
changes depending on the Disconnect on Inactivity configuration.
When the app state changes to clientBackgrounded
or screenSaverWillStart
, to save its state, the app needs to call the Cloud module’s saveInstanceState
with the folly::dynamic
object parameter that includes its current and screen history stack.
The Cloud module constant savedInstanceState
must be checked when the application is being launched.
If this state is present, it is decoded with the JSON parser to recreate the current screen and screen history portion of its Redux state.
To be able to restore the application properly, the application needs to store a variety of UI-specific information for each screen, such as current focus and list scroll positions.
Commonly, this UI-specific information is already stored as part of the app’s state when working with Redux or React Navigation. If it’s not, the app needs to add additional logic to persist this information. The data for these values could be stored as follows:
Current focus: an app-generated ID that is associated with a particular focusable element
<Button onFocus={() => {/* Update focus state for screen */ } />
List scroll position: a key-value pair that matches a list’s app-generated ID with an index
({index}) => <Button onFocus={() => {/* Update scrollIndex for list */ />}
As reference IDs are not guaranteed to be consistent with sessions, the app must generate its own IDs that are associated with on-screen elements.
The application’s performance can be impacted depending on how you save the data. As this information might not be needed until the application is restored, consider a strategy that does not re-render the application unnecessarily. One option is to store disconnect-specific information in a map, which can be accessed by each component through a React Context.
When restoring the application, lists can restore their position using the key-value mapping to set the lists initialScrollIndex, as contentOffset is currently unsupported with You.i React Native. Download the zip file to see the reference implementation of the persistent module that is used to restore the UI information.