├── .gitignore ├── LICENSE ├── README.md ├── SwapiAndroidApp ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── jniLibs │ │ ├── arm64 │ │ │ └── libswapi.so │ │ ├── armeabi │ │ │ └── libswapi.so │ │ └── x86 │ │ │ └── libswapi.so │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── rust │ │ │ └── app │ │ │ └── swapiclient │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── rust │ │ │ │ └── app │ │ │ │ └── swapiclient │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── adapter │ │ │ │ └── PeopleAdapter.kt │ │ │ │ ├── nativeswapi │ │ │ │ └── NativeSwapiClient.kt │ │ │ │ └── swapi │ │ │ │ ├── InternalPointerMarker.java │ │ │ │ ├── JNIReachabilityFence.java │ │ │ │ ├── Logger.kt │ │ │ │ ├── People.java │ │ │ │ ├── SwapiClient.java │ │ │ │ └── SwapiPeopleLoadedListener.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ └── item_people.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── rust │ │ └── app │ │ └── swapiclient │ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── SwapiIOSApp ├── SwapiClientIOS.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── igor.steblii.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── igor.steblii.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── SwapiClientIOS │ ├── .gitignore │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── SceneDelegate.swift │ ├── Swapi-Bridging-Header.h │ ├── model │ ├── People.swift │ └── PeopleResponse.swift │ ├── swapi │ ├── NativeSwapiClient.swift │ └── SwapiLoader.swift │ ├── swapi_core.h │ ├── view │ └── PeopleRow.swift │ └── vm │ └── PeopleViewModel.swift ├── build_release ├── clean ├── content ├── bridge_arch.png ├── swapi_adr.png └── swapi_ios.png ├── swapi_adr ├── .gitignore ├── Cargo.toml ├── Readme.md ├── build.rs ├── scripts │ ├── create_s_link │ └── release_build └── src │ ├── android_logger.rs │ ├── lib.rs │ ├── main.rs │ └── swig │ ├── java_glue.rs │ ├── java_glue.rs.in │ └── mod.rs ├── swapi_core ├── .gitignore ├── Cargo.toml ├── Readme.md ├── build.rs └── src │ ├── lib.rs │ ├── main.rs │ ├── swapi.rs │ └── swapi_core.h └── swapi_ios ├── .gitignore ├── Cargo.toml ├── Readme.md ├── build.rs ├── scripts └── release_build └── src ├── ffi ├── callback.rs ├── ios_ffi.rs ├── mod.rs ├── native_model.rs └── swapi_native.h ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | # Local History for Visual Studio Code 3 | .history/ 4 | *.project -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Igor Steblii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swapi-rust-mobile 2 | Crossplatform mobile solution based on Rust for IOS & Android. 3 | 4 | ***More details here:["Rust & cross-platform mobile development"](https://medium.com/@igor.stebliy/rust-cross-platform-mobile-development-9117a67ac9b7)*** 5 | 6 | In this project you'll find Rust network client, that laods and serialize data from 7 | [star wars API](https://swapi.dev/people) and pass it to Android and IOS. 8 | 9 | Android | IOS 10 | :-------------------------:|:-------------------------: 11 | ![](/content/swapi_adr.png) | ![](/content/swapi_ios.png) 12 | 13 | # Architecture 14 | 15 | Project consist of sub-project: 16 | * Android applicaition 17 | * IOS application 18 | * Rust core module 19 | * Rust Android bridge 20 | * Rust IOS bridge 21 | 22 | Architecture | 23 | :-------------------------:| 24 | | 25 | 26 | ## swapi_core 27 | module wtih shared logic between IOS& Android. 28 | 29 | More details: [Readme](/swapi_core/Readme.md) 30 | 31 | ## swapi_adr 32 | Android specific module, that depends on [swapi_core] for networking and provide custom build for JNI communicaiton. 33 | 34 | More details: [Readme](/swapi_adr/Readme.md) 35 | 36 | ## swapi_ios 37 | IOS specific module, that depends on [swapi_code] for networking and provide custom build for FFI communicaiton. 38 | 39 | More details: [Readme](/swapi_ios/Readme.md) 40 | 41 | # Build & Run 42 | 43 | ## Enviroment project 44 | To setup enviroment follow instuctions in : 45 | * swapi_adr/Readme. 46 | * swapi_ios/Readme. 47 | * swapi_core/Readme. 48 | 49 | ### Official guides 50 | 51 | * [Android](https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html) 52 | * [IOS](https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-06-rust-on-ios.html) 53 | 54 | ## Build: 55 | 56 | Each project contain `scripts` folder to automate build. 57 | 58 | ### IOS: 59 | * ./swapi_ios/scripts/build_release 60 | 61 | ### Andorid: 62 | * ./swapi_adr/scripts/build_release 63 | * ./swapi_adr/scripts/create_s_link - create symb link between generated files .so and Android project `jniLibs` 64 | 65 | ## Build: 66 | * ./build_release (execute all 3 commands listed on top) 67 | * ./clean - clean all 3 Rust project 68 | 69 | # IDE 70 | 71 | Based on my experience 72 | 73 | ## Rust has very good support with: 74 | * InteliJ IDE CE 75 | * [Rust Plugin](https://intellij-rust.github.io/) 76 | 77 | ## Alternatvie: 78 | * VSCode 79 | * [Rust plugin](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust) 80 | -------------------------------------------------------------------------------- /SwapiAndroidApp/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | .gradle 88 | /local.properties 89 | .idea 90 | .DS_Store 91 | /build 92 | /captures 93 | .cxx 94 | 95 | #VSCode 96 | .vscode/* 97 | !.vscode/settings.json 98 | !.vscode/tasks.json 99 | !.vscode/launch.json 100 | !.vscode/extensions.json 101 | *.code-workspace 102 | 103 | # Local History for Visual Studio Code 104 | .history/ -------------------------------------------------------------------------------- /SwapiAndroidApp/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /SwapiAndroidApp/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlinx-serialization' 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | defaultConfig { 11 | applicationId "com.cvs.app.swapiclient" 12 | minSdkVersion 15 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | 19 | sourceSets { 20 | main { 21 | jniLibs.srcDirs = ["jniLibs"] 22 | } 23 | } 24 | 25 | ndk.abiFilters 'armeabi','arm64','x86' 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(dir: 'libs', include: ['*.jar']) 39 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 40 | implementation 'androidx.appcompat:appcompat:1.1.0' 41 | implementation 'androidx.core:core-ktx:1.2.0' 42 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 43 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 44 | 45 | //coroutines 46 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4' 47 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4' 48 | //serialization 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // or "kotlin-stdlib-jdk8" 50 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" // JVM dependency 51 | 52 | testImplementation 'junit:junit:4.12' 53 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 54 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 55 | } 56 | -------------------------------------------------------------------------------- /SwapiAndroidApp/app/jniLibs/arm64/libswapi.so: -------------------------------------------------------------------------------- 1 | /Users/igor.steblii/Project/Rust/swapi/swapi-rust-mobile/swapi_adr/target/aarch64-linux-android/release/libswapi.so -------------------------------------------------------------------------------- /SwapiAndroidApp/app/jniLibs/armeabi/libswapi.so: -------------------------------------------------------------------------------- 1 | /Users/igor.steblii/Project/Rust/swapi/swapi-rust-mobile/swapi_adr/target/armv7-linux-androideabi/release/libswapi.so -------------------------------------------------------------------------------- /SwapiAndroidApp/app/jniLibs/x86/libswapi.so: -------------------------------------------------------------------------------- 1 | /Users/igor.steblii/Project/Rust/swapi/swapi-rust-mobile/swapi_adr/target/i686-linux-android/release/libswapi.so -------------------------------------------------------------------------------- /SwapiAndroidApp/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /SwapiAndroidApp/app/src/androidTest/java/com/rust/app/swapiclient/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.rust.app.swapiclient 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.cvs.app.swapiclient", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SwapiAndroidApp/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SwapiAndroidApp/app/src/main/java/com/rust/app/swapiclient/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rust.app.swapiclient 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.widget.Button 7 | import android.widget.Toast 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.rust.app.swapiclient.adapter.PeopleAdapter 11 | import com.rust.app.swapiclient.nativeswapi.NativeSwapiClient 12 | import com.rust.app.swapiclient.swapi.Logger 13 | import com.rust.app.swapiclient.swapi.People 14 | import com.rust.app.swapiclient.swapi.SwapiClient 15 | import com.rust.app.swapiclient.swapi.SwapiPeopleLoadedListener 16 | 17 | class MainActivity : AppCompatActivity() { 18 | 19 | companion object { 20 | const val TAG = "MainActivity" 21 | 22 | init { 23 | System.loadLibrary("swapi") 24 | } 25 | } 26 | 27 | private lateinit var recyclerView: RecyclerView 28 | private lateinit var viewAdapter: PeopleAdapter 29 | private lateinit var viewManager: RecyclerView.LayoutManager 30 | 31 | private val client by lazy(LazyThreadSafetyMode.NONE) { SwapiClient() } 32 | 33 | private val nativeClient by lazy(LazyThreadSafetyMode.NONE) { NativeSwapiClient() } 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | setContentView(R.layout.activity_main) 38 | Logger.initLogger() 39 | setupRecycler() 40 | findViewById