Is it possible to combine a GUI made with react native with utility methods from native code (kotlin)?

Viewed 315

I have an app which was made entirely using react native. It's pretty much a todo list, this is the structure:

<SafeAreaView style={styles.container}>
        <Text style={styles.title} >Registro de pacientes</Text>
        {loading && (
          <ActivityIndicator
            style={styles.loading}
            size="large"
            color="#0066ff"
          />
        )}
        {!loading && (
          <ListaPacientes  
            pacientes={pacientes} 
            onUpdate={this.handleUpdate}
          />
        )}
      </SafeAreaView>

And here is the list:

<SectionList
    sections={
      pacientes && pacientes.length ? 
      [
        {title: "Pacientes sin registrar", data: pacientes.filter(paciente => !paciente.done)},
        {title: "Pacientes registrados", data: pacientes.filter(paciente => paciente.done)}
      ] : []
    }
    keyExtractor={paciente => paciente.id}
    renderItem={({item}) => renderItem(item)}
    renderSectionHeader={renderSectionHeader}
    style={styles.container}
    ItemSeparatorComponent={renderSeparator}
    ListEmptyComponent={renderEmptyComponent}
    stickySectionHeadersEnabled={true}
  />

This takes a list of clients and displays their names. Upon being clicked on, they are removed from the list. Very simple.

The thing is, the list of clients must be read from a csv file and, after being clicked, update the csv. I already have a piece of code that does that. However, it's written in kotlin (It was coded for another project).

Instead of having to do the whole fetching/reading/treating/updating of the data again in react native, can I somehow use the kotlin code I already have? If so, care to provide an example on how it would be done?

EDIT: To clarify, I have a kotlin method that returns an ArrayList of strings with the names I want to store on the list. How can I call that method from the javascript, obtain the arraylist, and use it as data source for my SectionList ?.

lateinit var pacientes: Array<String>

@ReactMethod
fun getListaPacientes() : Array<String> {
    return pacientes
}

Something like this?


  var res = []
  res = ListaPacientesModulo.getListaPacientes()

Thank you.

2 Answers

I have solved the problem doing the following:

First of all, turns out you can't use native modules if you are using expo, so I had to eject it.

Second, in order to use kotlin you have to install the plugin manually (which you can do, but I decided it wasn't worth the hassle so I used java instead)

When it comes to the code, you need to use "WritableArray" and "WritableMap".

private static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException {
    WritableMap map = new WritableNativeMap();

    Iterator<String> iterator = jsonObject.keys();
    while (iterator.hasNext()) {
        String key = iterator.next();
        Object value = jsonObject.get(key);
        if (value instanceof JSONObject) {
            map.putMap(key, convertJsonToMap((JSONObject) value));
        } else if (value instanceof Boolean) {
            map.putBoolean(key, (Boolean) value);
        } else if (value instanceof Integer) {
            map.putInt(key, (Integer) value);
        } else if (value instanceof Double) {
            map.putDouble(key, (Double) value);
        } else if (value instanceof String) {
            map.putString(key, (String) value);
        } else {
            map.putString(key, value.toString());
        }
    }
    return map;
}

All react methods must return void, use promises or callbacks to comunicate with react

@ReactMethod
public void readCSV(Promise promise) {

    //Load the list
    for(int i = 0; i < source.size(); i++) {
        this.listWithPeople.add(new Person(source.get(i), startTimes.get(i)));
    }

    //Pepare the map
    WritableArray defList = new WritableNativeArray();
    Gson g = new Gson();

    for(Person p : this.listWithPeople) {
        JSONObject jo = new JSONObject(g.toJson(p));
        WritableMap wm = convertJsonToMap(jo);
        defList.pushMap(wm);
    }


    promise.resolve(defList);

}

Finally, on your .js file:

import { NativeModules } from 'react-native';

const { NativeModuleName } = NativeModules;

const getPacientes = async () => {
    var peopleList = []

    var res = await NativeModuleName.readCSV()

    for(var i = 0; i < res.length; i++) {
        peopleList.push(newPerson({ name: res[i].name, star: res[i].date, done: false }))
    }

    return peopleList
}

On the android part you also need this class:

ModuleNamePackage.java

public class ModuleNamePackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> nativeModules = new ArrayList<>();
        nativeModules.add(new NativeModuleName(reactContext));
        return nativeModules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

And inside MainApplication.java, you need to add your package to the package list:

private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
            packages.add(new ModuleNamePackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };

Assuming that your Kotlin code is well built, ie, each method is detached from the UI, you can create a bridge from react-native to Kotlin.

You need to create a Package, a Module, and your Bridge class.

import com.facebook.react.bridge.ReactMethod

class CsvManager(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    // the bridge 
    @ReactMethod
    fun update(csvUrl: String, data: List<String>) {
        // your code to load or update the CSV
    }
}

In your top project, create a new JS like kotlinCsv.js :

import { NativeModules } from 'react-native'
module.exports = NativeModules.CsvModule

Anywhere in your App:

import KotlinCsvUpdater from './kotlinCsv'

let myArray = []
KotlinCsvUpdater.update('pathToMyFile', myArray)
Related