Focus showcase - Data persistence and state management
Updated: Sep 29, 2025
Store multiple room configurations
To maintain application state between sessions, use Android Shared Preferences (or an equivalent) to save data that persists after the application has been closed.
Focus enables the creation of multiple objects, and each project requires storing various data types locally on your device. These include the position and rotation of spatial objects, states, associated text, and other properties. While Android Shared Preferences can be used for this purpose, SQLite may be a better solution for more complex data structures.
Focus has implemented a DatabaseManager class to create, save, and retrieve data from a database.
There are five tables with different elements and attributes: Projects, Unique Assets, Tools, Sticky Notes and Tasks. Implemented methods include the DatabaseManager to create, get, update, and delete the elements in these tables.
Use components to save and retrieve object data and state
In Focus, the elements are split into two categories:
Unique Assets: Elements that are unique in the scene and cannot be deleted, like the Clock, Speaker, Tasks Panel, and AI Exchange Panel.
Tools: Elements the user can create and delete, like Boards, Sticky Notes, Tasks, Labels, Arrows, Shapes, Stickers, and Timers.
The database must be updated whenever these elements change state or position.
ECS is the core of Spatial SDK. To identify different objects in Focus, there are three custom components to attach to the entities:
Create a helper system to update objects’ positions
Focus uses a helper system that updates the position and rotation of objects in a database. It uses Spatial SDK systems and queries to check if an object has been grabbed and updates its position and rotation accordingly.
Create a class that inherits from SystemBase.
Register the system in your activity.
In the execute function, create a query to pull data from the data model, and get all elements that have a UniqueAsset or Tool component.
Check if any element in the lists has been grabbed. If so, update the position and rotation of the element in the database.
Consider running this process periodically to improve performance.
// ImmersiveActivity.kt
systemManager.registerSystem(DatabaseUpdateSystem())
// DatabaseUpdateSystem.kt
class DatabaseUpdateSystem : SystemBase() {
private var lastTime = System.currentTimeMillis()
override fun execute() {
if (ImmersiveActivity.instance.get()?.appStarted == false) return
val currentTime = System.currentTimeMillis()
// if there is no current project, we don't update database
if (ImmersiveActivity.instance.get()?.currentProject == null) {
lastTime = currentTime
}
// Check if objects are being moved and save new position
// We do this each 0.2 seconds to improve performance
val deltaTime = (currentTime - lastTime) / 1000f
if (deltaTime > 0.2) {
lastTime = currentTime
// Update pose of tool assets
val tools = Query.where { has(ToolComponent.id) }
for (entity in tools.eval()) {
val asset = entity.getComponent<ToolComponent>()
val pose = entity.getComponent<Transform>().transform
val isGrabbed = entity.getComponent<Grabbable>().isGrabbed
if (isGrabbed) {
if (asset.type != AssetType.TIMER) ImmersiveActivity.instance.get()?.DB?.updateAssetPose(asset.uuid, asset.type, pose)
}
}
// Update pose of unique assets
val uniqueAssets = Query.where { has(UniqueAssetComponent.id) }
for (entity in uniqueAssets.eval()) {
val uniqueAsset = entity.getComponent<UniqueAssetComponent>()
val pose = entity.getComponent<Transform>().transform
val isGrabbed = entity.getComponent<Grabbable>().isGrabbed
if (isGrabbed) ImmersiveActivity.instance.get()?.DB?.updateUniqueAsset(uniqueAsset.uuid, pose)
}
}
}
}
You can detect keyboard events in Focus, like when a user has finished writing text. This is useful for updating text in the database. To do this, follow these steps:
Add two properties to the EditText in the .XML file: android:inputType=”text” and android:imeOptions=”actionDone”.
Set an action listener to the EditText using setOnEditorActionListener to perform an action once the user finishes writing.
For longer texts that span multiple lines, add a TextWatcher to the EditText to detect when the user stops writing.
Use the addEditTextListeners function from Utils.kt to add these listeners to any EditText and choose whether to update the text when the action is done, or when the user stops writing.