https://github.com/bidmcdigitalpsychiatry/lamp-core-android

Core scaffolding for digital phenotyping apps with reactive user interfaces. (Android)

https://github.com/bidmcdigitalpsychiatry/lamp-core-android

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (13.8%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

Core scaffolding for digital phenotyping apps with reactive user interfaces. (Android)

Basic Info
  • Host: GitHub
  • Owner: BIDMCDigitalPsychiatry
  • Language: Kotlin
  • Default Branch: master
  • Homepage: https://docs.lamp.digital/
  • Size: 75.2 MB
Statistics
  • Stars: 3
  • Watchers: 5
  • Forks: 2
  • Open Issues: 1
  • Releases: 18
Created about 7 years ago · Last pushed 12 months ago
Metadata Files
Readme

README.md

Native Gradle Sample using a Node Project folder

An Android Studio project that uses the Node.js on Mobile shared library, as an example of using a Node Project folder inside the Application.

The sample app runs the node.js engine in a background thread to start an HTTP server on port 3000. The app's Main Activity UI has a button to query the server and show the server's response (i.e. the process.versions value, alongside the result of using the left-pad npm module). Alternatively, it's also possible to access the server from a browser running on a different device connected to the same local network.

How to run

  • Clone this project.
  • Run npm install inside android/native-gradle-node-folder/app/src/main/assets/nodejs-project/.
  • Download the Node.js on Mobile shared library from here.
  • Copy the bin/ folder from inside the downloaded zip file to app/libnode/bin (There are copy-libnode.so-here files in each architecture's path for convenience). If it's been done correctly you'll end with the following paths for the binaries:
    • app/libnode/bin/arm64-v8a/libnode.so
    • app/libnode/bin/armeabi-v7a/libnode.so
    • app/libnode/bin/x86/libnode.so
    • app/libnode/bin/x86_64/libnode.so
  • In Android Studio import the android/native-gradle/ gradle project. It will automatically check for dependencies and prompt you to install missing requirements (i.e. you may need to update the Android SDK build tools to the required version (25.0.3) and install CMake to compile the C++ file that bridges Java to the Node.js on Mobile library).
  • After the gradle build completes, run the app on a compatible device.

How the sample was developed

This sample was built on top of the native-gradle sample from this repo, with the same functionality, but uses a nodejs-project folder that contains the node part of the project.

Create the nodejs-project folder

Create a nodejs-project folder inside the project, in Gradle's default folder for Android's application assets (app/src/main/assets/nodejs-project). Create the main.js and package.json files inside:

  • app/src/main/assets/nodejs-project/main.js contents: js var http = require('http'); var versions_server = http.createServer( (request, response) => { response.end('Versions: ' + JSON.stringify(process.versions)); }); versions_server.listen(3000); console.log('The node project has started.');

  • app/src/main/assets/nodejs-project/package.json contents: js { "name": "native-gradle-node-project", "version": "0.0.1", "description": "node part of the project", "main": "main.js", "author": "janeasystems", "license": "" }

Add an npm module to the nodejs-project

Having a nodejs-project path with a package.json inside is helpful for using npm modules, by running npm install {module_name} inside nodejs-project so that the modules are also packaged with the application and made available at runtime.

Install the left-pad module, by running npm install left-pad inside the app/src/main/assets/nodejs-project/ folder.

Update app/src/main/assets/nodejs-project/main.js to use the module: js var http = require('http'); var leftPad = require('left-pad'); var versions_server = http.createServer( (request, response) => { response.end('Versions: ' + JSON.stringify(process.versions) + ' left-pad: ' + leftPad(42, 5, '0')); }); versions_server.listen(3000); console.log('The node project has started.');

Copy the nodejs-project at runtime and start from there

To start the Node.js engine runtime with a file path, we need to first copy the project to somewhere in the Android file system, because the Android Application's APK is an archive file and Node.js won't be able to start running from there. For this purpose, we choose to copy the nodejs-project into the Application's FilesDir.

Add the helper functions to app/src/main/java/com/yourorg/sample/MainActivity.java: ```java import android.content.Context; import android.content.res.AssetManager;

...

private static boolean deleteFolderRecursively(File file) {
    try {
        boolean res=true;
        for (File childFile : file.listFiles()) {
            if (childFile.isDirectory()) {
                res &= deleteFolderRecursively(childFile);
            } else {
                res &= childFile.delete();
            }
        }
        res &= file.delete();
        return res;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

private static boolean copyAssetFolder(AssetManager assetManager, String fromAssetPath, String toPath) {
    try {
        String[] files = assetManager.list(fromAssetPath);
        boolean res = true;

        if (files.length==0) {
            //If it's a file, it won't have any assets "inside" it.
            res &= copyAsset(assetManager,
                    fromAssetPath,
                    toPath);
        } else {
            new File(toPath).mkdirs();
            for (String file : files)
            res &= copyAssetFolder(assetManager,
                    fromAssetPath + "/" + file,
                    toPath + "/" + file);
        }
        return res;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

private static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath) {
    InputStream in = null;
    OutputStream out = null;
    try {
        in = assetManager.open(fromAssetPath);
        new File(toPath).createNewFile();
        out = new FileOutputStream(toPath);
        copyFile(in, out);
        in.close();
        in = null;
        out.flush();
        out.close();
        out = null;
        return true;
    } catch(Exception e) {
        e.printStackTrace();
        return false;
    }
}

private static void copyFile(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int read;
    while ((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
    }
}

```

Before starting the node runtime, delete the previous nodejs-project and copy the current one into the FilesDir and start the runtime from there: java new Thread(new Runnable() { @Override public void run() { //The path where we expect the node project to be at runtime. String nodeDir=getApplicationContext().getFilesDir().getAbsolutePath()+"/nodejs-project"; //Recursively delete any existing nodejs-project. File nodeDirReference=new File(nodeDir); if (nodeDirReference.exists()) { deleteFolderRecursively(new File(nodeDir)); } //Copy the node project from assets into the application's data path. copyAssetFolder(getApplicationContext().getAssets(), "nodejs-project", nodeDir); startNodeWithArguments(new String[]{"node", nodeDir+"/main.js" }); } }).start();

Attention: Given the project folder can be overwritten, it should not be used for persistent data storage.

Copy the nodejs-project only after an APK change

Recopying the nodejs-project at each Application's run can be expensive, so improve it by saving the last time the APK was updated on an Application Shared Preference and check if we need to delete and copy the nodejs-project.

Add the helper functions to app/src/main/java/com/yourorg/sample/MainActivity.java: ```java import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.SharedPreferences;

...

private boolean wasAPKUpdated() {
    SharedPreferences prefs = getApplicationContext().getSharedPreferences("NODEJS_MOBILE_PREFS", Context.MODE_PRIVATE);
    long previousLastUpdateTime = prefs.getLong("NODEJS_MOBILE_APK_LastUpdateTime", 0);
    long lastUpdateTime = 1;
    try {
        PackageInfo packageInfo = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0);
        lastUpdateTime = packageInfo.lastUpdateTime;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return (lastUpdateTime != previousLastUpdateTime);
}

private void saveLastUpdateTime() {
    long lastUpdateTime = 1;
    try {
        PackageInfo packageInfo = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0);
        lastUpdateTime = packageInfo.lastUpdateTime;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    SharedPreferences prefs = getApplicationContext().getSharedPreferences("NODEJS_MOBILE_PREFS", Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = prefs.edit();
    editor.putLong("NODEJS_MOBILE_APK_LastUpdateTime", lastUpdateTime);
    editor.commit();
}

```

Change the code that starts the node runtime to check if it needs to delete the previous nodejs-project and copy the current one into the FilesDir:

```java new Thread(new Runnable() { @Override public void run() { //The path where we expect the node project to be at runtime. String nodeDir=getApplicationContext().getFilesDir().getAbsolutePath()+"/nodejs-project"; if (wasAPKUpdated()) { //Recursively delete any existing nodejs-project. File nodeDirReference=new File(nodeDir); if (nodeDirReference.exists()) { deleteFolderRecursively(new File(nodeDir)); } //Copy the node project from assets into the application's data path. copyAssetFolder(getApplicationContext().getAssets(), "nodejs-project", nodeDir);

                    saveLastUpdateTime();
                }
                startNodeWithArguments(new String[]{"node",
                        nodeDir+"/main.js"
                });
            }
        }).start();

```

Redirect the stdout and stderr to logcat

The Node.js runtime and the Node.js console module use the process' stdout and stderr streams. Some code is needed to redirect those streams to the Android system log, so they can be viewed with logcat. This sample adds C++ code to manage the redirection by starting two background threads (one for stdout and the other for stderr), to provide a more pleasant Node.js debugging experience.

Add the helper functions to app/src/main/cpp/native-lib.cpp: ```cc

include

include

include

...

// Start threads to redirect stdout and stderr to logcat. int pipestdout[2]; int pipestderr[2]; pthreadt threadstdout; pthreadt threadstderr; const char *ADBTAG = "NODEJS-MOBILE";

void threadstderrfunc(void) { ssizet redirectsize; char buf[2048]; while((redirectsize = read(pipestderr[0], buf, sizeof buf - 1)) > 0) { //_androidlog will add a new line anyway. if(buf[redirectsize - 1] == '\n') --redirectsize; buf[redirectsize] = 0; _androidlogwrite(ANDROIDLOGERROR, ADBTAG, buf); } return 0; }

void threadstdoutfunc(void) { ssizet redirectsize; char buf[2048]; while((redirectsize = read(pipestdout[0], buf, sizeof buf - 1)) > 0) { //_androidlog will add a new line anyway. if(buf[redirectsize - 1] == '\n') --redirectsize; buf[redirectsize] = 0; _androidlogwrite(ANDROIDLOGINFO, ADBTAG, buf); } return 0; }

int startredirectingstdoutstderr() { //set stdout as unbuffered. setvbuf(stdout, 0, _IONBF, 0); pipe(pipestdout); dup2(pipestdout[1], STDOUTFILENO);

//set stderr as unbuffered.
setvbuf(stderr, 0, _IONBF, 0);
pipe(pipe_stderr);
dup2(pipe_stderr[1], STDERR_FILENO);

if(pthread_create(&thread_stdout, 0, thread_stdout_func, 0) == -1)
    return -1;
pthread_detach(thread_stdout);

if(pthread_create(&thread_stderr, 0, thread_stderr_func, 0) == -1)
    return -1;
pthread_detach(thread_stderr);

return 0;

} ```

Start the redirection right begore starting the Node.js runtime: ```cc //Start threads to show stdout and stderr in logcat. if (startredirectingstdoutstderr()==-1) { _androidlogwrite(ANDROIDLOGERROR, ADBTAG, "Couldn't start redirecting stdout and stderr to logcat."); }

//Start node, with argc and argv.
return jint(node::Start(argument_count,argv));

```

Owner

  • Name: BIDMC Division of Digital Psychiatry
  • Login: BIDMCDigitalPsychiatry
  • Kind: organization
  • Email: team@digitalpsych.org
  • Location: Longwood Medical Area, Boston, MA, US

GitHub Events

Total
  • Release event: 2
  • Watch event: 2
  • Delete event: 1
  • Push event: 26
  • Pull request event: 7
  • Fork event: 1
  • Create event: 10
Last Year
  • Release event: 2
  • Watch event: 2
  • Delete event: 1
  • Push event: 26
  • Pull request event: 7
  • Fork event: 1
  • Create event: 10

Dependencies

WearOs/app/build.gradle maven
  • com.google.android.wearable:wearable 2.7.0 compileOnly
  • android.arch.lifecycle:extensions 1.1.0 implementation
  • androidx.appcompat:appcompat 1.1.0 implementation
  • androidx.constraintlayout:constraintlayout 1.1.3 implementation
  • androidx.fragment:fragment-ktx 1.3.6 implementation
  • androidx.legacy:legacy-support-v4 1.0.0 implementation
  • androidx.lifecycle:lifecycle-extensions $lifecycle_version implementation
  • androidx.lifecycle:lifecycle-livedata-ktx 2.2.0 implementation
  • androidx.lifecycle:lifecycle-runtime-ktx 2.2.0 implementation
  • androidx.percentlayout:percentlayout 1.0.0 implementation
  • androidx.recyclerview:recyclerview 1.1.0 implementation
  • com.google.android.gms:play-services-wearable 17.0.0 implementation
  • com.google.android.support:wearable 2.7.0 implementation
  • com.google.firebase:firebase-analytics 17.4.4 implementation
  • com.google.firebase:firebase-auth 19.3.2 implementation
  • com.google.firebase:firebase-core 17.4.4 implementation
  • com.google.firebase:firebase-database 19.3.1 implementation
  • com.google.firebase:firebase-messaging 20.2.3 implementation
  • com.squareup.okhttp3:logging-interceptor 4.2.1 implementation
  • com.squareup.retrofit2:converter-gson 2.6.2 implementation
  • com.squareup.retrofit2:retrofit 2.6.2 implementation
  • org.jetbrains.kotlin:kotlin-stdlib-jdk7 $kotlin_version implementation
WearOs/sensormodule/build.gradle maven
  • com.github.denzilferreira:aware-client master-SNAPSHOT api
  • androidx.appcompat:appcompat 1.1.0 implementation
  • androidx.core:core-ktx 1.3.1 implementation
  • com.squareup.retrofit2:converter-gson $retrofit_version implementation
  • org.jetbrains.kotlin:kotlin-stdlib-jdk7 $kotlin_version implementation
  • junit:junit 4.12 testImplementation
app/build.gradle maven
  • androidx.appcompat:appcompat 1.4.1 implementation
  • androidx.constraintlayout:constraintlayout 2.0.4 implementation
  • androidx.core:core-ktx 1.7.0 implementation
  • androidx.lifecycle:lifecycle-extensions $lifecycle_version implementation
  • androidx.lifecycle:lifecycle-livedata-ktx $lifecycle_version implementation
  • androidx.lifecycle:lifecycle-runtime-ktx $lifecycle_version implementation
  • androidx.room:room-ktx $room_version implementation
  • androidx.room:room-runtime $room_version implementation
  • androidx.work:work-runtime-ktx 2.7.1 implementation
  • com.github.BIDMCDigitalPsychiatry:LAMP-kotlin 2022.6.10 implementation
  • com.github.BIDMCDigitalPsychiatry:LAMP-kotlin 12OS-SNAPSHOT implementation
  • com.github.BIDMCDigitalPsychiatry:LAMP-kotlin Notification-SNAPSHOT implementation
  • com.github.BIDMCDigitalPsychiatry:LAMP-kotlin master-SNAPSHOT implementation
  • com.github.haroldadmin:WhatTheStack 0.3.0 implementation
  • com.google.android.gms:play-services-auth 20.2.0 implementation
  • com.google.android.gms:play-services-fitness 21.1.0 implementation
  • com.google.android.material:material 1.6.1 implementation
  • com.google.firebase:firebase-analytics-ktx * implementation
  • com.google.firebase:firebase-crashlytics-ktx * implementation
  • com.google.firebase:firebase-messaging-ktx * implementation
  • com.squareup.okhttp3:okhttp 4.9.0 implementation
  • com.squareup.retrofit2:adapter-rxjava2 $retrofit_version implementation
  • com.squareup.retrofit2:converter-gson $retrofit_version implementation
  • com.squareup.retrofit2:converter-moshi $retrofit_version implementation
  • com.squareup.retrofit2:retrofit $retrofit_version implementation
  • org.jetbrains.kotlin:kotlin-stdlib-jdk7 $kotlin_version implementation
  • junit:junit 4.13.1 testImplementation
.github/workflows/publish.yml actions
  • actions/checkout v2 composite
  • actions/setup-java v1 composite
  • actions/upload-artifact v1 composite
  • chkfung/android-version-actions v1.1 composite
  • r0adkll/sign-android-release v1 composite
  • r0adkll/upload-google-play v1 composite
.github/workflows/wearos.yml actions
  • actions/checkout v2 composite
  • actions/setup-java v1 composite
  • actions/upload-artifact v1 composite
  • chkfung/android-version-actions v1.1 composite
  • r0adkll/sign-android-release v1 composite
  • r0adkll/upload-google-play v1 composite
WearOs/build.gradle maven
build.gradle maven