Traditionally, imperative programming required developers to specify every step in building and updating a UI. Declarative frameworks, on the other hand, allow developers to define what the UI should look like for a given state, while the framework takes care of rendering and updates. Mobile development has taken a significant leap forward with the rise of declarative UI frameworks. SwiftUI for iOS, Jetpack Compose for Android, and React for web development allow developers to efficiently build responsive user interfaces. Unlike cross-platform tools like React Native, these frameworks retain the full power of their respective platforms while adopting a declarative approach. This article explores how to harness the strengths of SwiftUI, Jetpack Compose, and React for parallel development across iOS, Android, and the web.
1. Preparing Data Bundles for Mobile Development
When developing mobile applications, it’s common to include assets such as JSON data, images, or other resources locally. Here’s how to prepare and access bundled data in iOS (SwiftUI) and Android (Jetpack Compose).
Comparison:
SwiftUI: Accessing Bundled Data
if let url = Bundle.main.url(forResource: "Books", withExtension: "json"),
let data = try? Data(contentsOf: url) {
let jsonString = String(data: data, encoding: .utf8)
print(jsonString ?? "No data")
}
Jetpack Compose: Accessing Bundled Data
val context: Context = LocalContext.current
val json = context.assets.open("Books.json").bufferedReader().use { it.readText() }
println(json)
2. Persistent Storage Solutions
Persistent storage is crucial for maintaining user preferences and lightweight data across sessions.
Comparison:
SwiftUI - UserDefaults
UserDefaults.standard.set("Sample Book", forKey: "LastOpenedBook")
let lastOpenedBook = UserDefaults.standard.string(forKey: "LastOpenedBook")
Jetpack Compose - SharedPreferences
sharedPreferences.edit().putString("LastOpenedBook", "Sample Book").apply()
val lastOpenedBook = sharedPreferences.getString("LastOpenedBook", null)
React - localStorage
localStorage.setItem("LastOpenedBook", "Sample Book");
const lastOpenedBook = localStorage.getItem("LastOpenedBook");
3. Layout Structures
Declarative frameworks simplify UI composition with flexible layout components:
-
SwiftUI:
VStack
andHStack
for vertical and horizontal layout. -
Jetpack Compose:
Column
andRow
, functioning similarly to Flexbox. -
React:
div
elements styled withdisplay: flex
orgrid
.
4. Data Passing Between Components
React Approach
Data is passed using props. The parent can pass state and setter functions for the child to modify state.
Example:
// Parent Component
function ParentComponent() {
const [message, setMessage] = useState("Hello from Parent");
return <ChildComponent message={message} setMessage={setMessage} />;
}
// Child Component
function ChildComponent({ message, setMessage }) {
return (
<div>
<p>{message}</p>
<button onClick={() => setMessage("Updated by Child")}>Update Message</button>
</div>
);
}
SwiftUI Approach
If the child component does not need to modify the parent’s state, a standard parameter can be passed:
// Parent View
struct ParentView: View {
@State private var message = "Hello from Parent"
var body: some View {
ChildView(message: message)
}
}
// Child View
struct ChildView: View {
var message: String
var body: some View {
VStack {
Text(message)
}
}
}
If the child component needs to modify the parent’s state, use @Binding
:
// Parent View
struct ParentView: View {
@State private var message = "Hello from Parent"
var body: some View {
ChildView(message: $message)
}
}
// Child View
struct ChildView: View {
@Binding var message: String
var body: some View {
VStack {
Text(message)
Button("Update Message") {
message = "Updated by Child"
}
}
}
}
Jetpack Compose Approach
State is passed via function arguments, with lambda functions for state modification.
Example:
// Parent Composable
@Composable
fun ParentComponent() {
var message by remember { mutableStateOf("Hello from Parent") }
ChildComponent(message = message, onMessageChange = { newMessage -> message = newMessage })
}
// Child Composable
@Composable
fun ChildComponent(message: String, onMessageChange: (String) -> Unit) {
Column {
Text(text = message)
Button(onClick = { onMessageChange("Updated by Child") }) {
Text("Update Message")
}
}
}
5. Sharing State Across Components
React: useContext
Provides a way to pass state throughout the component tree without prop drilling.
Example:
const MessageContext = React.createContext();
function ParentComponent() {
const [message, setMessage] = useState("Hello from Context");
return (
<MessageContext.Provider value={{ message, setMessage }}>
<DeepChildComponent />
</MessageContext.Provider>
);
}
function DeepChildComponent() {
const { message, setMessage } = useContext(MessageContext);
return (
<div>
<p>{message}</p>
<button onClick={() => setMessage("Updated by Deep Child")}>Update Message</button>
</div>
);
}
SwiftUI: @EnvironmentObject
Used for sharing data across views.
Example:
class SharedDataModel: ObservableObject {
@Published var message = "Hello from Environment"
}
struct ParentView: View {
@StateObject var sharedData = SharedDataModel()
var body: some View {
DeepChildView().environmentObject(sharedData)
}
}
struct DeepChildView: View {
@EnvironmentObject var sharedData: SharedDataModel
var body: some View {
VStack {
Text(sharedData.message)
Button("Update Message") {
sharedData.message = "Updated by Deep Child"
}
}
}
}
Jetpack Compose: CompositionLocalProvider
Works like React’s Context API for providing global state.
Example:
val LocalMessage = compositionLocalOf { "Default Message" }
@Composable
fun ParentComponent() {
var message by remember { mutableStateOf("Hello from CompositionLocal") }
CompositionLocalProvider(LocalMessage provides message) {
DeepChildComponent()
}
}
@Composable
fun DeepChildComponent() {
val message = LocalMessage.current
Column {
Text(text = message)
}
}
Conclusion
The shift to declarative programming in SwiftUI, Jetpack Compose, and React has redefined UI development. Each framework simplifies building modern, responsive UIs. While choosing between cross-platform and native solutions or experimenting with transpilers like Skip, it’s clear that technologies such as ChatGPT can bridge coding across languages, making multi-platform development more feasible.
Top comments (0)