How to use OpenCombine framework with Android Studio
Tags: android, scade, ios, swift
Published: 2024-01-27 07:02:06

---
title: How to use OpenCombine framework with Android Studio
published: true
description: 
tags: android, scade, ios, swift
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bq78a5pc82xx8yfowj6k.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-11-19 23:23 +0000
---


## Introduction

📱Namaste Mobile Developers Community 🙏! In the ever-evolving software development landscape, the demand for cross-platform solutions is more pronounced than ever. Developers seek tools that enable them to transfer their skills and codebases between different platforms seamlessly. When it comes to reactive programming in Swift, Apple's [Combine](https://developer.apple.com/documentation/combine) framework stands as a robust solution. However, what if you want to extend the power of Combine to Android development?

Enter **[OpenCombine](https://github.com/OpenCombine/OpenCombine)** – an open-source implementation of Combine that opens the door to cross-platform reactive programming. With its declarative syntax, support for Combine-like features, and cross-platform compatibility, OpenCombine stands as a key player in the realm of reactive programming for Swift developers.

In the ongoing series of _Swift for Android_ articles, we will delve into the intricacies of using OpenCombine with Android Studio, empowering developers to harness the magic of reactive programming on both Swift and Android. We will build a simple Task Management app where from Android’s activity the tasks can be created or deleted using Swift’s OpenCombine framework. The OpenCombine processes the tasks-data over time and updates the Android’s UI(Recyclerview) with the latest data.

## Prerequisite

If you haven’t installed the SCADE IDE, [download](https://www.scade.io/download/) the SCADE IDE and [install](https://docs.scade.io/docs/installation) it on your macOS system. The only prerequisite for SCADE is the Swift language (at least basics). Also, please ensure that Android Studio with an Android emulator or physical device is running to test the Android application. 

To know more about how to add Swift to existing Android projects, please go through these articles:



1. Display list of GitHub followers using Github API & Swift PM library - [https://medium.com/@SCADE/implement-recyclerview-using-swift-pm-libraries-eacc1efd48af](https://medium.com/@SCADE/implement-recyclerview-using-swift-pm-libraries-eacc1efd48af) 

2. Using Swift PM libraries in the Android Studio projects - [https://medium.com/@SCADE/using-swift-pm-libraries-in-the-android-studio-projects-7cef47c300bf](https://medium.com/@SCADE/using-swift-pm-libraries-in-the-android-studio-projects-7cef47c300bf) 

### Source Code of App 
You can directly jump to the source code of the Task Management App using OpenCombine. 
https://drive.google.com/file/d/1Eq12boTsBkUpg8VYQPUo79pyDbReNIiq/view?usp=sharing 

## What is OpenCombine?

OpenCombine is an open-source implementation of the Combine framework, which is designed for reactive programming in Swift. It enables developers to work with asynchronous and event-driven code in a more declarative and concise manner. Combine is particularly powerful in the context of SwiftUI, where changes in data trigger automatic updates to the user interface. But it also works perfectly with the Android Studio Swift toolchain.

### Key Features of OpenCombine:

1. **Publishers and Subscribers:** Open Source Combine framework revolves around the concepts of publishers that emit values and subscribers that receive and react to those values.

2. **Operators:** Combine provides a set of operators that allow developers to transform, filter, and combine streams of values.

3. **Error Handling:** Combine includes mechanisms for handling errors in a reactive way, making it well-suited for handling asynchronous tasks.

4. **Declarative Syntax:** With Combine, developers can express complex asynchronous logic in a more declarative manner, making the code more readable and maintainable.

### Integrate OpenCombine using SPM

To seamlessly integrate OpenCombine into your Swift project, we can add the dependency using Swift Package Manager (SPM) by modifying the `Package.swift` file.

  


```swift
dependencies: [
   .package(url: "https://github.com/scade-platform/swift-java.git", branch: "main"),
   .package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.10.0")
],
```


These declarations ensure that the necessary frameworks are included, fostering a unified development environment that harnesses the combined power of Swift and Java for cross-platform scenarios.


```swift
.target(
   name: "SwiftAndroidExample",
   dependencies: [
           .product(name: "Java", package: "swift-java"),
           .product(name: "OpenCombine", package: "OpenCombine"),
   ]),
```


## Create the Task-App's Backend in Swift

Before diving into creating and deleting functionality for Tasks, it's imperative to set the groundwork by importing the necessary packages.


```swift
import Dispatch
import Foundation
import Java
import OpenCombine
```


The Swift OpenCombine code encapsulates task management logic, featuring a singleton `TaskListViewModel` with reactive capabilities through a `tasksPublisher`. Designed for a cross-platform app, this backend seamlessly integrates with an Android UI, allowing consistent task handling across Swift and Android platforms. The use of shared business logic ensures a unified approach to task management in the application.

1. **Task Struct:**

   - There's a `Task` struct representing a model for a task with two properties: `id` of type `Int` and `title` of type `String`. The struct conforms to the `Identifiable` protocol.

2. **TaskListViewModel Class:**

   - `TaskListViewModel` is a class responsible for managing tasks.

   - It's a singleton class with a static instance accessible through `viewModel`.

   - The initializer is marked as private, ensuring that instances of this class can only be created from within the class itself.

3. **Tasks Array:**

   - The class contains a `tasks` property, which is an array of `Task` objects. This array serves as the storage for the tasks managed by the view model.

4. **Tasks Publisher:**

   - There's a `tasksPublisher` property, which is an instance of `PassthroughSubject&lt;[Task], Never>`. This subject acts as a publisher for changes in the tasks array.

5. **addTask Method:**

   - The `addTask` method adds a new task to the `tasks` array, prints a message indicating that the task has been added, and then sends the updated array through the `tasksPublisher`.

6. **removeTask Method:**

   - The `removeTask` method removes a task from the `tasks` array based on its index, prints a message indicating that the task has been removed, and sends the updated array through the `tasksPublisher`.

7. **getTasksResultString Method:**

   - The `getTasksResultString` method returns the string array containing all the current tasks.

This `TaskListViewModel` is designed for managing a collection of tasks, providing methods for adding, removing, and retrieving task information. The use of a publisher (`tasksPublisher`) allows for reactive programming, notifying subscribers of changes in the tasks array.


```swift
// Model for a task
struct Task: Identifiable {
 let id: Int
 let title: String
}

// ViewModel to manage tasks
class TaskListViewModel {
 static let viewModel = TaskListViewModel()
 private init() {
 }
 var tasks: [Task] = []
 let tasksPublisher = CurrentValueSubject<[Task], Never>([])
 private var cancellables: [AnyCancellable] = [] // Use non-optional array

 func addTask(_ task: Task) {
   tasks.append(task)
   print("Task added: \(task.title)")
   tasksPublisher.send(tasks)
 }

 func removeTask(_ task: Task) {
   tasks.remove(at: task.id)
   print("Task removed: \(task.title)")
   tasksPublisher.send(tasks)
 }

   func getTasksResultString() -> [String] {
    var taskStringArray = [String]()
    print("Current tasks:")
    for task in tasks {
      taskStringArray.append(task.title)
    }
    return taskStringArray
  }

 func subscribeToChanges(handler: @escaping ([Task]) -> Void) {
         tasksPublisher
             .sink { tasks in
                 handler(tasks)
             }
             .store(in: &cancellables)
 }
}
```


## Design UI for Task Management App

The activity’s XML layout defines the user interface for the task management app on Android. Key UI widgets to be added in XML include:

1. **RecyclerView for Task List:**

   - The `RecyclerView` with the ID `recyclerview` is the focal point for displaying the list of tasks. It's configured to match the parent width and have a dynamic height based on its content.

2. **Floating Action Button (FAB) for Adding Tasks:**

   - The `FloatingActionButton` with the ID `fab` serves as a prominent button for adding new tasks. It is positioned at the bottom right of the screen and adorned with a plus icon.

3. **Top Layout for App Logo and Title:**

   - The `topLayout` contains an `ImageView` displaying the app logo (`scade_logo`) and a `TextView` presenting the title "Task Management App." This layout is oriented vertically and centered on the screen.

4. **Layout for No Tasks Present:**

   - The `noTasksPresentLayout` is a container that holds a `TextView` with a message ("No Tasks present. Add a Task!"). It is initially set to `gone` and becomes visible when there are no tasks to display.


```xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <LinearLayout
       android:id="@+id/topLayout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">

       <ImageView
           android:layout_width="95dp"
           android:layout_height="wrap_content"
           android:layout_gravity="center"
           android:layout_margin="4dp"
           android:adjustViewBounds="true"
           android:src="@drawable/scade_logo" />

       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_gravity="center"
           android:layout_marginTop="8dp"
           android:gravity="center"
           android:padding="8dp"
           android:text="Task Management App"
           android:textColor="@color/black"
           android:textSize="20sp"
           android:textStyle="bold" />

   </LinearLayout>

   <LinearLayout
       android:id="@+id/layoutTasks"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_below="@+id/topLayout"
       android:layout_margin="8dp"
       android:orientation="vertical">

       <androidx.recyclerview.widget.RecyclerView
           android:id="@+id/recyclerview"
           android:layout_width="match_parent"
           android:layout_height="wrap_content" />

   </LinearLayout>

   <com.google.android.material.floatingactionbutton.FloatingActionButton
       android:id="@+id/fab"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignParentRight="true"
       android:layout_alignParentBottom="true"
       android:layout_gravity="bottom|end"
       android:layout_margin="16dp"
       app:srcCompat="@android:drawable/ic_input_add" />

   <LinearLayout
       android:id="@+id/noTasksPresentLayout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_centerHorizontal="true"
       android:layout_centerVertical="true"
       android:orientation="vertical"
       android:visibility="gone">

       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_gravity="center"
           android:gravity="center"
           android:padding="8dp"
           android:text="No Tasks present. \nAdd a Task!"
           android:textColor="@color/red"
           android:textSize="18sp" />
   </LinearLayout>
</RelativeLayout>
```


The structure emphasizes a clean and intuitive design, where the `RecyclerView` is central for visualizing tasks, and user prompts are presented elegantly for adding tasks when the list is empty. Also, We will dynamically add tasks at runtime, and an `EditText` is employed within an `AlertDialog` for user input at the click of `fab` button. 

## Create RecyclerView Adapter to Display Tasks

The Android `RecyclerViewAdapter` efficiently connects a list of task models to a `RecyclerView`. It utilizes a `MyViewHolder` to define the appearance of each task, with a `TextView` displaying the task title. 


```java
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
   private Context context;
   private List<String> taskModels;
   private final OnTaskItemClickListner taskItemClickListner;

   public RecyclerViewAdapter(Context context, List<String> taskModels, OnTaskItemClickListner taskItemClickListner) {
       this.context = context;
       this.taskModels = taskModels;
       this.taskItemClickListner = taskItemClickListner;
   }

   public interface OnTaskItemClickListner {
       void onTaskItemClick(String taskModel);

       boolean onTaskItemLongClick(String taskModel);
   }

   @NonNull
   @Override
   public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
       View itemView = LayoutInflater.from(context).inflate(R.layout.task_item_layout, parent, false);
       return new MyViewHolder(itemView);
   }

   @Override
   public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
       holder.bind(taskModels.get(position), taskItemClickListner);
   }

   @Override
   public int getItemCount() {
       return taskModels.size();
   }

   class MyViewHolder extends RecyclerView.ViewHolder {
       TextView titleTV;

       public MyViewHolder(@NonNull View itemView) {
           super(itemView);
           titleTV = (TextView) itemView.findViewById(R.id.titleTV);
       }

       public void bind(final String taskModel, final OnTaskItemClickListner taskItemClickListner) {
           titleTV.setText(taskModel);
           itemView.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View view) {
                   taskItemClickListner.onTaskItemClick(taskModel);
               }
           });

           itemView.setOnLongClickListener(new View.OnLongClickListener() {
               @Override
               public boolean onLongClick(View view) {
                   return taskItemClickListner.onTaskItemLongClick(taskModel);
               }
           });
       }
   }
}
```


The adapter's interface, `OnTaskItemClickListner`, facilitates item click and long-click events. Clicking an item triggers the `onTaskItemClick` method, while a long-click invokes `onTaskItemLongClick`. This streamlined adapter ensures the separation of concerns, managing UI-related tasks, and delegating interaction events to the implementing class.

## Define Swift Methods in Android’s activity

We will first initialize the Swift runtime in activity by calling `SwiftFoundation.Initialize`. The first argument is a pointer to the Java context (activity), and the second argument is always set to false. Then it loads the dynamic library "SwiftAndroidExample" containing Swift code using `System.loadLibrary`. 


```java
try {
   // initializing swift runtime.
   // The first argument is a pointer to java context (activity in this case).
   // The second argument should always be false.
   org.swift.swiftfoundation.SwiftFoundation.Initialize(this, false);
} catch (Exception err) {
   android.util.Log.e("SwiftAndroidExample", "Can't initialize swift foundation: " + err.toString());
}

// loading dynamic library containing swift code
System.loadLibrary("SwiftAndroidExample");
```


### Initialize the TaskManager class 

The Android declaration introduces the native method `initTaskManager()`, acting as a bridge between Java and Swift in the Android app. \
```java
private native void initTaskManager();
```


Its Swift implementation, denoted by `MainActivity_initTaskManager`, leverages the `@_silgen_name` attribute for native function linkage. This Swift method, residing in the `MainActivity`, initializes the Task Manager by creating a `JObject` wrapper for the Android activity object. It then prepares the associated Swift `TaskListViewModel` by calling the `updateTasksList` method, ensuring seamless integration and readiness for adding or deleting tasks within the Android app.


```swift
// NOTE: Use @_silgen_name attribute to set native name for a function called from Java
@_silgen_name("Java_com_example_swiftandroidexample_MainActivity_initTaskManager")
public func MainActivity_initTaskManager(
 env: UnsafeMutablePointer<JNIEnv>, activity: JavaObject
) {
  // Create JObject wrapper for activity object
  let mainActivity = JObject(activity)
  let viewModel = TaskListViewModel.viewModel

  viewModel.subscribeToChanges { _ in
    updateTasksList(activity: mainActivity)
  }

  updateTasksList(activity: mainActivity)
}
```


### Native Methods to add/delete a Task

The Android native methods `addTask` and `removeTask`, declared in the Java activity, find their Swift implementations through the `@_silgen_name` attribute, ensuring seamless cross-language integration. 


```java
public native void addTask(String taskName);

public native void removeTask(String taskName);
```


The Swift `MainActivity_addTask` method receives a task name from Java, converts it to a Swift string, creates a new task, and adds it to the `TaskListViewModel`. Similarly, `MainActivity_removeTask` removes the specified task from the viewModel's task list. Both Swift methods conclude by calling `updateTasksList` to synchronize the changes back to the Android activity, demonstrating a bi-directional flow of task management between Java and Swift in the Android app.


```swift
// NOTE: Use @_silgen_name attribute to set native name for a function called from Java
@_silgen_name("Java_com_example_swiftandroidexample_MainActivity_addTask")
public func MainActivity_addTask(
 env: UnsafeMutablePointer<JNIEnv>, activity: JavaObject,
 taskName: JavaString
) {
 // Create JObject wrapper for activity object
 let mainActivity = JObject(activity)

 // Convert the Java string to a Swift string
 let taskTitle = String.fromJavaObject(taskName)

 let viewModel = TaskListViewModel.viewModel

 let task1 = Task(id: viewModel.tasks.count, title: taskTitle)

 viewModel.addTask(task1)
}
```
```swift
// NOTE: Use @_silgen_name attribute to set native name for a function called from Java
@_silgen_name("Java_com_example_swiftandroidexample_MainActivity_removeTask")
public func MainActivity_removeTask(
 env: UnsafeMutablePointer<JNIEnv>, activity: JavaObject,
 taskName: JavaString
) {
 // Create JObject wrapper for activity object
 let mainActivity = JObject(activity)

 // Convert the Java string to a Swift string
 let taskTitle = String.fromJavaObject(taskName)

 let viewModel = TaskListViewModel.viewModel

 for task in viewModel.tasks {
   if task.title == taskTitle {
     viewModel.removeTask(task)
   }
 }
}
```


### Display current Tasks in RecyclerView

In the Swift code, the `updateTasksList` method orchestrates the synchronization of task data between Swift and Java in the Android app. By accessing the `TaskListViewModel`, it retrieves the result string representing the current tasks. This string is then passed to the Java activity through the `activity.call` method, invoking the `printTasks` method in Java.


```swift
public func updateTasksList(activity: JObject) {
  let viewModel = TaskListViewModel.viewModel
  let tasksList = viewModel.getTasksResultString()
  activity.call(method: "printTasks", tasksList)
}
```


The corresponding `printTasks` method in Java receives the task result string. If the string is empty, it adjusts the visibility of UI elements to prompt the user to add tasks. Otherwise, it parses the tasks from the string, configures a `RecyclerViewAdapter` with a click listener for removing tasks, and dynamically updates the RecyclerView to display the tasks in the Android activity. This seamless interaction exemplifies the bidirectional communication between Swift and Java for effective task management in cross-platform applications.


```java
    public void printTasks(String[] taskResultString) {
        if (taskResultString.length == 0) {
            // show add tasks
            findViewById(R.id.noTasksPresentLayout).setVisibility(View.VISIBLE);
            findViewById(R.id.layoutTasks).setVisibility(View.GONE);
            return;
        }
        findViewById(R.id.noTasksPresentLayout).setVisibility(View.GONE);
        findViewById(R.id.layoutTasks).setVisibility(View.VISIBLE);
        List<String> tasks = getTasksFromString(taskResultString);
        RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(MainActivity.this, tasks, new RecyclerViewAdapter.OnTaskItemClickListner() {
            @Override
            public void onTaskItemClick(String taskModel) {
                showDialogToRemoveTask(taskModel);
            }

            @Override
            public boolean onTaskItemLongClick(String taskModel) {
                return false;
            }
        });
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(recyclerViewAdapter);
    }
```


### Implement Dialog to Remove Tasks

The `showDialogToRemoveTask` method in Android presents an `AlertDialog` to confirm the removal of a task. The dialog displays the task name in the title, providing clarity to the user. The positive button, labeled "Delete," triggers the `removeTask` method if clicked, initiating the removal process. On the negative button, labeled "Cancel," the dialog is dismissed, offering users the option to reconsider. This interactive and user-friendly dialog, coupled with Swift's implementation of the `removeTask` method, ensures a seamless and intuitive task removal experience in the cross-platform application.


```java
private void showDialogToRemoveTask(String taskModel) {
   AlertDialog.Builder builder = new AlertDialog.Builder(this);
   builder.setTitle("Remove the task: " + taskModel);

   // Set up the buttons
   builder.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           removeTask(taskModel);
       }
   });
   builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           dialog.cancel();
       }
   });

   builder.show();

}
```


## Run & Test the App!

Let's test the app now & check if OpenCombine smoothly updates the task list in the RecyclerView as we add or remove the tasks. Keep an eye on the user interface, and ensure the tasks reflect real-time changes, validating the effectiveness of OpenCombine in seamless cross-platform communication.

### Add Task:




![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/csh8b9mw919rh9793ynv.gif)


### Remove Task:




![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jt56dsry818yqyawjpq9.gif)


Trying out OpenCombine in Android Studio for our task management app was pretty cool! It brings the power of reactive Swift programming to Android, making cross-platform development a breeze. Give it a shot and if you run into any hiccups, hop into our [SCADE’s Discord community](https://discord.gg/ECxbtgx2ct) – we're always here to help! 🚀