Support for Android Instant Apps with Android App Bundle.
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
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