realm-java icon indicating copy to clipboard operation
realm-java copied to clipboard

Support for Android Instant Apps with Android App Bundle.

Open gerardoepitacio opened this issue 3 years ago • 1 comments

How frequently does the bug occur?

All the time

Description

When I use the Instant App of my application which is distributed through Google Play, I am getting this error while I tried to use a realm operation to wait for synchronization between Realm and Mongo db or doing an insert operation like:

override suspend fun upsertPartial(key: String, value: String?): ObjectId {
        var objectId: ObjectId? = null
        val insertedValue: PartialEntity? = this.getPartialByKey(key)
        val realm: Realm = Realm.getDefaultInstance()
        realm.executeTransactionAwait(this.dispatcher) {
            objectId = insertedValue?.partialId ?: ObjectId()
            val partialEntity = PartialEntity(
                partialId = objectId!!,
                partitionKey = this.invitationPartitionKey,
                invitationId = this.invitationId,
                key = key,
                value = value
            )

// ERROR
            realm.insertOrUpdate(partialEntity)
        }

        realm.close()

        return objectId!!
    }

INSTALLED VERSION OF THE APP DOESN'T THROW THIS ERROR!

Stacktrace & log output

2022-02-03 13:15:28.639 18543-18823/? E/REALM_JNI: jni: ThrowingException 7, Permission denied in /tmp/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp line 107, .
2022-02-03 13:15:28.640 18543-18823/? E/REALM_JNI: Exception has been thrown: Permission denied in /tmp/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp line 107

Can you reproduce the bug?

Yes, always

Reproduction Steps

Initializing a realm application like:

object RealmProviderManager {

    private const val TAG = "RealmProviderManager"

    private var realmApplication: App? = null

    private var user: User? = null

    private var realmSyncConfiguration: SyncConfiguration? = null

    private var syncSession: SyncSession? = null

    suspend fun initialize(context: Context, realmAppId: String, partitionKey: String): Boolean {

        return try {

            Realm.init(context)

            if (BuildConfig.DEBUG) {

                RealmLog.setLevel(LogLevel.ALL)
            }

            val realmAppConfiguration = AppConfiguration.Builder(realmAppId)
                .build()

            this.realmApplication = App(realmAppConfiguration)

            this.initialize(this.realmApplication!!, partitionKey)

            this.initializeRealmSession()

            true
        } catch (ex: Exception) {

            // TODO log error to telemetry

            false
        }
    }

    suspend fun initialize(realmApplication: App, partitionKey: String) {

        this.realmApplication = realmApplication

        this.user = this.loginToRealm()

        realmSyncConfiguration = SyncConfiguration.Builder(
            this.user,
            partitionKey
        ).build()

        if(Realm.getDefaultConfiguration()?.realmFileName != realmSyncConfiguration?.realmFileName) {

            Realm.setDefaultConfiguration(realmSyncConfiguration!!)
        }
    }

    private suspend fun loginToRealm(): User? {

        return suspendCancellableCoroutine { continuation ->

            // TODO update this to use a custom JWT token or maybe a custom function
            val credentials: Credentials = Credentials.anonymous()

            realmApplication!!.loginAsync(credentials) { loginResult ->

                if (loginResult.isSuccess) {

                    val loginUser: User? = loginResult.get()

                    continuation.resume(loginUser) {

                        // implementation not required
                    }
                }
                // otherwise the exception will be thrown in the coroutine
                // and this should be catch in the consumer of this function
                else {

                    val error: AppException = loginResult.error

                    continuation.resumeWithException(error)
                }
            }
        }
    }

    private fun initializeRealmSession() {

        this.syncSession = realmApplication?.sync?.getOrCreateSession(realmSyncConfiguration)

        syncSession?.start()
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    suspend fun waitToDownloadComplete(): Boolean {

        val realm: Realm = Realm.getDefaultInstance()

        return suspendCancellableCoroutine { cancelableContinuation ->

            syncSession?.addDownloadProgressListener(ProgressMode.INDEFINITELY) { progress ->

                if(progress.isTransferComplete) {

                    cancelableContinuation.resume(true){

                        // Implementation not required
                    }

                    realm.close()
                }
            }
        }
    }

In my view model I call the function like.

class SynchronizationViewModel @Inject constructor(
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ViewModel() {

fun initializeViewModel() {

        this.viewModelScope.launch(this.dispatcher) {

            try {
// ...
RealmProviderManager.waitToDownloadComplete()
            } catch (exception: Exception) {
// ...
            }
        }
    }

Version

10.8.0, 10.10.1

What SDK flavour are you using?

MongoDB Realm (i.e. Sync, auth, functions)

Are you using encryption?

No, not using encryption

Platform OS and version(s)

Android, Android Instant App

Build environment

Android Studio version

Android Studio Bumblebee | 2021.1.1 Build #AI-211.7628.21.2111.8092744, built on January 18, 2022 Runtime version: 11.0.11+0-b60-7590822 x86_64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. macOS 11.6.2 GC: G1 Young Generation, G1 Old Generation Memory: 2048M Cores: 8 Registry: external.system.auto.import.disabled=true Non-Bundled Plugins: com.dengzii.plugin.adb (2021.1.3), com.wakatime.intellij.plugin (13.1.8), idea.plugin.protoeditor (2.3.1), net.codestats.plugin.atom.intellij (1.0.9), app.teamhub (14.0.6), wu.seal.tool.jsontokotlin (3.7.2), com.thoughtworks.gauge (211.6693.111), com.google.mad-scorecard (1.2), org.intellij.plugins.markdown (211.7142.37)

Android Build Tools version Android Gradle Plugin version: 7.0.4

Gradle version (gradle-wrapper.properties): #Thu Nov 25 18:31:09 CST 2021 distributionBase=GRADLE_USER_HOME distributionUrl=https://services.gradle.org/distributions/gradle-7.0.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME

gerardoepitacio avatar Feb 03 '22 20:02 gerardoepitacio

Instant apps have a limitation that does not allow creating named pipes, required by the realm commit notifier. It has been reported to google and they are looking into it, see https://issuetracker.google.com/issues/203772220

clementetb avatar Feb 23 '22 16:02 clementetb