Cache Implementation in Cross-Platform Swift Apps
Implement persistence in Cross-Platform Swift apps!
Introduction
Have you ever used a mobile app that remembered your login information or preferences, even after you closed the app? This is thanks to the magic of SharedPreferences
in Android and UserDefaults
in iOS.
š In this article, we'll explore how these two features work to store and retrieve key-value data in a cross-platform Swift app. š We'll start with an overview of what SharedPreferences and UserDefaults are and how they differ from each other. š” Then, we'll dive into some examples of how to use them in your Android and iOS apps and show you how to make your app's data persistent even when the user closes the app.
Whether you're a seasoned mobile developer or just getting started with cross-platform development, understanding how to use SharedPreferences
and UserDefaults
is an essential skill. So, let's get started and explore how these features can make your app more user-friendly and efficient! š
SharedPreference & UserDefaults
First, let's talk about SharedPreferences, which is a lightweight and efficient way to store key-value pairs in Android. To use SharedPreferences in a cross-platform Swift app, you can use a library such as SwiftyUserDefaults or UserDefaultsWrapper. These libraries provide a convenient and type-safe way to access SharedPreferences in Swift, using syntax similar to that of UserDefaults in iOS.
Speaking of UserDefaults, this is a built-in mechanism in iOS that allows apps to store and retrieve user preferences and other small amounts of data. To use UserDefaults in a cross-platform Swift app, you can create a wrapper class that abstracts away the platform-specific details and provides a unified API for accessing user defaults.
Prerequisite
If you havenāt installed the SCADE IDE, download the SCADE IDE and install it on your macOS system. The only prerequisite for SCADE is the Swift language (at least basics). Also, please ensure the Android emulator or physical device is running if you want to run SCADE apps on an Android device.
We will try to implement saving the key-value pairs in Cache for both Android & iOS specific environments. One of the common uses of persistence is saving the login result after successful login. We will build a simple login screen and try to save the result if the authentication is successful.
Source Code
Design a SCADE Page
Letās quickly design a screen with input fields Userās name & email. There will be a button to save the key-value pairs. Similarly, there will be other two labels and a button to fetch the key-value pairs stored in Cache.
Code for iOS
For the iOS platform, we will use UserDefaults in the Foundation
package to store key-value pairs. UserDefaults is a built-in tool in iOS that provides a simple way to store small amounts of data persistently between app launches. It is commonly used to store user preferences, settings, or small pieces of data.
You can access the UserDefaults
object anywhere in your SCADE IDE. To get started, you should create an instance of UserDefaults.
Letās save the current name and email string text to some variables.
var currEmailText = ""
var currNameText = ""
self.email_tb.onTextChange.append(
SCDWidgetsTextChangeEventHandler { ev in
currEmailText = ev!.newValue
})
self.name_tb.onTextChange.append(
SCDWidgetsTextChangeEventHandler { ev in
currNameText = ev!.newValue
})
Then, attach the onClick
listener to the Save button and call the saveTocache()
method which saves the key-value pairs to persistence.
self.save_btn.onClick { _ in
print("Email: \(currEmailText) Name: \(currNameText)")
if currEmailText.isEmpty || currEmailText.isEmpty {
print("Name or Email is empty")
return
}
self.saveToCache(email: currEmailText, name: currNameText)
}
As per the code snippet, it uses UserDefaults
to save the persistence. Here, the keys are āemailā and ānameā which stores the values.
func saveToCache(email: String, name: String) {
#if os(iOS)
UserDefaults.standard.set(email, forKey: "email")
UserDefaults.standard.set(name, forKey: "name")
#endif
}
Now letās fetch the Key-Value pairs
Now we will fetch the values for the keys stored in the Cache. Letās write generic methods to get the values for the keys as Integer, String, and boolean values. It will use the same UserDefaults.standard.*
property to get the values.
func retrieveIntegerFromCache(key: String) -> Int {
#if os(iOS)
return UserDefaults.standard.integer(forKey: key) ?? 0
#endif
return 0
}
func retrieveStringFromCache(key: String) -> String {
#if os(iOS)
return UserDefaults.standard.string(forKey: key) ?? ""
#endif
return ""
}
func retrieveBoolFromCache(key: String) -> Bool {
#if os(iOS)
return UserDefaults.standard.bool(forKey: key) ?? false
#endif
return false
}
Code for Android
We will now add the Android code in Swift to implement SharedPreferences
. You can refer here for details about how the Android SDK SharedPreferences classes are mapped to the Swift equivalent.
Now letās add the Android Swift library to the Package.swift
.
// swift-tools-version:5.3
import Foundation
import PackageDescription
let SCADE_SDK = ProcessInfo.processInfo.environment["SCADE_SDK"] ?? ""
let package = Package(
name: "ScadeProject",
platforms: [
.macOS(.v10_14)
],
products: [
.library(
name: "ScadeProject",
type: .static,
targets: [
"ScadeProject"
]
)
],
dependencies: [
.package(
name: "ScadeExtensions", url: "https://github.com/scade-platform/ScadeExtensions",
.branch("main")),
.package(
name: "Android", url: "https://github.com/scade-platform/swift-android.git",
.branch("android/24")),
],
targets: [
.target(
name: "ScadeProject",
dependencies: [
.product(name: "Android", package: "Android", condition: .when(platforms: [.android])),
.product(name: "AndroidOS", package: "Android", condition: .when(platforms: [.android])),
.product(name: "AndroidApp", package: "Android", condition: .when(platforms: [.android])),
.product(
name: "AndroidContent", package: "Android", condition: .when(platforms: [.android])),
],
exclude: ["main.page"],
swiftSettings: [
.unsafeFlags(["-F", SCADE_SDK], .when(platforms: [.macOS, .iOS])),
.unsafeFlags(["-I", "\(SCADE_SDK)/include"], .when(platforms: [.android])),
]
)
]
)
As the next step, import the Android packages and use the SharedPreferences methods to save/retrieve the key-value pairs.
#if os(Android)
import Android
import AndroidContent
import AndroidApp
import AndroidOS
import Foundation
import Java
#endif
We will now use the SharedPreferencesEditor
which will be used to update/fetch the key-value pairs from the SharedPreferences
.
#if os(Android)
private var currentActivity: Activity? { Application.currentActivity }
var delegate: SharedPreferences?
var delegateEditor: AndroidContent.SharedPreferencesEditor?
#endif
Then, initialize the variables delegate
and delegateEditor
using the activity instance. Here, we will use the mode as 0(MODE_PRIVATE
) to allow both read and write operations privately by application with the keyword-defined āSCADEā instance.
#if os(Android)
delegate = Application.currentActivity?.getSharedPreferences(name: "SCADE", mode: 0)
delegateEditor = delegate?.edit()
#endif
Letās write separate methods for saving and fetching the values using the delegateEditor
instance.
Saving the Key-Value pairs
public func putInt(value: Int32, key: String) {
let val32:Int32 = value
delegateEditor!.putInt(key: key, value: val32)
delegateEditor!.commit()
}
public func putBoolean(value: Bool, key: String) {
delegateEditor!.putBoolean(key: key, value: value)
delegateEditor!.commit()
}
public func putString(value: String, key: String) {
delegateEditor!.putString(key: key, value: value)
delegateEditor!.commit()
}
public func putFloat(value: Float, key: String) {
delegateEditor!.putFloat(key: key, value: value)
delegateEditor!.commit()
}
Fetching the Key-Value pairs
public func getInt(key: String) -> Int32 {
let intValue:Int32 = (delegate?.getInt(key: key, defValue: 0)) ?? 0
return intValue
}
public func getString(key: String) -> String {
let strValue:String = delegate?.getString(key: key, defValue: "DEFAULT_VALUE_STRING") ?? ""
return strValue
}
public func getBoolean(key: String) -> Bool {
let boolValue:Bool = delegate?.getBoolean(key: key, defValue: false) ?? false
return boolValue
}
public func getFloat(key: String) -> Float {
let floatValue:Float = delegate?.getFloat(key: key, defValue: 0.0) ?? 0.0
return floatValue
}
public func containsKey(key: String) -> Bool {
return delegate!.contains(key: key)
}
Now we will use the above methods wherever required to save and fetch the values for the keys: āemailā & ānameā. Hence the below method is used to update the values.
func retrieveStringFromCache(key: String) -> String {
#if os(iOS)
return UserDefaults.standard.string(forKey: key) ?? ""
#endif
#if os(Android)
return self.getString(key: key)
#endif
return ""
}
After fetching the values stored in the applicationās cache, update the text for name & email labels.
self.fetch_btn.onClick { _ in
print(
"retrieve from cache: \(self.retrieveStringFromCache(key: "email")) \(self.retrieveStringFromCache(key: "name"))"
)
self.email_fetch.text = "Email: \(self.retrieveStringFromCache(key: "email"))"
self.name_fetch.text = "Name: \(self.retrieveStringFromCache(key: "name"))"
}
Run the App on iOS/Android
In order to run the app on iOS/Android devices, please ensure the physical devices or simulator/emulator are up and running. SCADE apps can be run on SCADE emulators, iOS & Android devices as well as Apple and Android emulators.
You can also build the app for Android (APK/AAB) or iOS (IPA) to publish them to the respective app stores. You need to click on the App Name button and choose the device target accordingly.
iOS
Android
Voila š! It was really interesting to create an iOS or Android app using the SCADE IDE by implementing Cache. It is really cool. We encourage you to implement SharedPreferences
with SCADEš.