Multiple realms in React Native don't query realm object server correctly on first launch of app after install - react-native

I am having an issue dealing with multiple realms in React Native. I'm working on an app that allows users to use the app without having a subscription (using a local realm) and then at any point in their app journey they have the option of upgrading to a subscription with syncing (which uses sync to a realm object server).
When I start the app I check to see if they are using sync and if so I initialize a synced realm with their user and everything works great. I get all the data I expect.
However, when the app starts on first launch after install (that part about first launch after install is crucial) and I see that they don't use sync I initialize a local realm which I save data to until they decide to log in to their sync account (if they have one). At this point I attempt to pull information from the synced realm but it does not have the information that I saw when I only initialized the synced realm (in the case that on app startup I detect they use sync).
I am able to log in as the sync user but the data isn't there if I've previously initialized a local realm AND this logic gets run on the first launch of the app after install. The data only shows up from the realm object server when I initialize a local and synced realm on a secondary launch of the app (no reinstall before launching).
Here's a simple test script with dummy data in it with which I've been able to replicate the observed behavior:
const username = 'testuser2';
const password = 'supersecret';
const tld = 'REALM_OBJECT_SERVER_TLD';
class Test extends Realm.Object {}
Test.schema = {
name: 'Test',
properties: {
id: {
type: 'string',
},
}
};
function initLocalRealm() {
return new Realm({
path: 'local.realm',
schema: [Test],
});
}
function initSyncedRealmWithUser(user) {
return new Realm({
path: 'synced.realm',
sync: {
user,
url: `realm://${tld}:9080/~/data`,
},
schema: [Test],
});
}
function writeTestObjectWithId(realm, id) {
realm.write(() => {
realm.create('Test', {
id,
});
alert(`Test object with id: ${id}`);
});
}
initLocalRealm();
// setup
// uncomment this and comment out the login section to setup user on first run
// Realm.Sync.User.register(`http://${tld}:9080`, username, password, (error, user) => {
// if (error) {
// return;
// }
// const syncedRealm = initSyncedRealmWithUser(user);
// writeTestObjectWithId(syncedRealm, '1');
// });
// login
Realm.Sync.User.login(`http://${tld}:9080`, username, password, (error, user) => {
if (error) {
return;
}
const syncedRealm = initSyncedRealmWithUser(user);
alert(`Synced realm test objects: ${syncedRealm.objects('Test').length}`);
});
If you create a react native app and then add this code to the main components componentDidMount function you should see that on the first run of the app (after you've uncommented the register code once) you will see the Test collection length at 0, but then when you refresh you will see the Test collection length at 1.
Any help on this would be awesome.
Thanks!

running your code snippet, I get a length of 1 immediately as soon as I uncomment the login section. Could you try observing your synchronized realm with the Realm Browser and see if it seems to have the data you are expecting after registering the user?

Related

How to update state machine

Is it possible to replace the currently initialized state machine in React?
For example, I initialize a state machine via Provider with a dummy configuration. Then, upon entering a specific page in the app, I update it via context.
Background of what I want to achieve: Consumer can either load the configuration globally or page-specific. Let's say for example that a required data for your config is unknown until you reach a specific portion of your page. So, only then you can properly create a state machine. Or, if consumer wants to keep the configuration per module/page. Bec doing this at the top level would mean, you have to collate all configurations which can get lengthy and hard to debug assuming you know the configuration in advance.
In provider
const dummyConfig = {
id: 'init',
initial: 'init_state',
states: {
init_state: {
on: {},
},
},
};
const initMachine = createMachine(dummyConfig);
const [stateMachine, setStateMachine] = useState(initMachine);
// stateMachine does not get updated with the new machine from the child component below
// passed via setMachine callback in the context
const [current, send, service] = useMachine(stateMachine);
const updateMachine = useCallback((machine: StateMachine) => {
setStateMachine(machine);
}, []);
return (
<MachineContext.Provider value={[current, send, service, updateMachine]}>
{children}
</StateMachineContext.Provider>
);
In child component somewhere in the app
const machine = createMachine(newConfig);
// Context hook
const { current, send, servicee, updateMachine } = useStateMachine();
// I can receive the new machine up in the provider (checked via console log),
// but it does not update the machine
// The value of current remains that from the first created machine
updateMachine(machine);

How to send session info and do automated page tracking in expo react native segment and amplitude integration

I am using Segment[expo-analytics-segment] to send tracking info to Amplitude(Configured as the destination in app.segment.com) in an expo react native app. Though I am sending session info(epoch time) - The session always gets registered as -1, hence I am unable to access 'funnel' feature in Amplitude.
Also - How do we enable automatic page tracking in expo segment+amplitude configuration?
This is what I have done so far in App.tsx
Segment.initialize({
androidWriteKey: 'androidKey', // from Segment
iosWriteKey: 'iOsKey', // from segment
});
global.epochInMilliSeconds = Date.now();
Segment.identifyWithTraits(
user.sub,
{ email: 'notgood#gmail.com' },
{
event: 'App Started',
integrations: {
Amplitude: {
sessionId: global.epochInMilliSeconds,
},
},
}
);
Segment.trackWithProperties(
'App Started',
{ email: 'fancyemail#gmail.com' },
{ integrations: { Amplitude: { session_id: global.epochInMilliSeconds } } }
); <------------------- Did not work. Session id is -1**
Segment.track('App Started'); // <-----------------------Session id is -1
More info - https://github.com/expo/expo/issues/10559
I followed this example for the above code sample: https://community.amplitude.com/instrumentation-and-data-management-57/how-do-we-set-session-in-amplitude-while-using-segment-in-cloud-mode-111
Amplitude website mentions that session Ids are not automatically tracked.
https://help.amplitude.com/hc/en-us/articles/217934128-Segment-Amplitude-Integration
In case the link changes, it says:
6. Why do all of my events have a sessionId of -1?
You need to use Segment's client-side bundled integration to have our native SDKs track Session IDs for you.

WebSQL threw an error [Error: Error code 1: no such table: document-store]

We are using react-naive-sqlite-2 in our application with RxDB and started getting this error somewhat sporadically. It seems to happen after we remove the database. I was surprised to see this was a WebSQL error since we are using react-native and WebSQL is deprecated. I don't have great ways to debug this but my hunch is that we have some code that still tries to access the now dead database.
This is the code we use to set up our database:
import SQLiteAdapterFactory from 'pouchdb-adapter-react-native-sqlite'
import SQLite from 'react-native-sqlite-2'
import { addRxPlugin, createRxDatabase } from 'rxdb'
import { RxDBReplicationGraphQLPlugin } from 'rxdb/plugins/replication-graphql'
import type { DatabaseType } from '../generated'
/**
* SQLITE SETUP
*/
const SQLiteAdapter = SQLiteAdapterFactory(SQLite)
addRxPlugin(SQLiteAdapter)
addRxPlugin(require('pouchdb-adapter-http'))
/**
* Other plugins
*/
addRxPlugin(RxDBReplicationGraphQLPlugin)
export const getRxDB = async () => {
return await createRxDatabase<DatabaseType>({
name: 'gatherdatabase',
adapter: 'react-native-sqlite', // the name of your adapter
multiInstance: false,
})
The issue happens after we logout and attempt to log back in. When we logout, we call removeRxDatabase. Has anyone ran into this kind of issue before or know of ways to debug?
For posterity, the issue was that we had a reference to the database in our state management library (Zustand) that was being held onto past logout. When we tried to login again, our getOrCreateDatabase function didn't make a new one but it wasn't valid since we had run database.remove() in rxdb. We ended up just clearing the Zustand db and calling database.remove() at one place.
export const useRxDB = create<UseRxDBType>((set, get) => ({
database: undefined,
/**
* we set the database to ready in the LocalDocument store once local docs are loaded into the store
*/
databaseIsReady: false,
getOrCreateDatabase: async () => {
let database = get().database
if (!database) {
database = await createRxDatabase()
if (!Rx.isRxDatabase(database)) throw new Error(`database isnt a valid RxDB database.`)
set({ database })
}
return database
},
resetDatabase: async () => {
const database = get().database
if (database) {
await database.remove()
set({ database: undefined })
}
},
}))

Firebase Nativescript Facebook Authentication onAuthStateChanged()

I have a Nativescript App built with Angular and I use the Nativescript Plugin Firebase: https://github.com/EddyVerbruggen/nativescript-plugin-firebase.
When I authenticate with Facebook, it retrieves me the user data successfully, but the user does not appear as logged in, and the onAuthStateChanged() listener is not triggered.
The function I use is the simplest one:
public async signInWithFacebookPopup(): Promise<any> {
return await firebase.login({
type: firebase.LoginType.FACEBOOK
}).then(
function (result) {
return result;
},
function (errorMessage) {
return {error: errorMessage};
}
);
}
So here the "result" I get is the actual user data.
Shouldn't the method onAuthStateChanged() be triggered? Also, after logging in and getting the user's data, If I call firebaseWeb.auth().currentUser, I get undefined.
[UPDATE]
When the Facebook window is displayed (before introducing my credentials), I get this error in the console:
chromium: [INFO:library_loader_hooks.cc(51)] Chromium logging enabled: level = 0, default verbosity = 0
chromium: [ERROR:filesystem_posix.cc(89)] stat /data/user/0/[APP_NAME]/cache/WebView/Crashpad: No such file or directory (2)
chromium: [ERROR:filesystem_posix.cc(62)] mkdir /data/user/0/[APP_NAME]/cache/WebView/Crashpad: No such file or directory (2)
Could it be that this one breaks some other behaviours later?

Dependency Injection (for HttpFetch) at setRoot in main.js Aurelia

I am having trouble getting dependency injection working for my AuthorizerService. Obviously, dep-inj is not ready until after Aurelia "starts", but I wasn't sure how to access it.
main.js:
aurelia.container.registerInstance(HttpClient, http.c());
// set your interceptors to take cookie data and put into header
return aurelia.start().then(() => {
let Authorizer = new AuthorizerService();
aurelia.container.registerInstance(AuthorizerService, Authorization);
console.log('Current State: %o', Authorizer.auth);
Authorizer.checkCookieAndPingServer().then(() => { console.log('Current State: %o', Authorizer.auth); aurelia.setRoot(PLATFORM.moduleName('app')); }, () => { aurelia.setRoot(PLATFORM.moduleName('login-redirect')); });
});
Now the problem is that if I do "new AuthorizerService()" then "this.http.fetch()" is not available in AuthorizerService.js.
Am I meant to pass "http.c()" (which delivers the HttpClient instance) as a parameter inside:
checkCookieAndPingServer(http.c())
or is there another way?
Can I delete "new AuthorizerService()" and just do (I made this up):
aurelia.container.getInstance(AuthorizerService);
Somehow FORCE it to do dependency-injection and retrieve the "registered Instance" of "http.c()"?
I can't just check cookie. I have to ping server for security and the server will set the cookie.
I think this is all sorts of wrong, because I need a global parameter that just is false by default, then it does the query to backend server and setsRoot accordingly. Perhaps only query backend in the "login page"? Okay but then I would need to do "setRoot(backtoApp); aurelia.AlsoSetLoggedIn(true);" inside login module. But when I setRoot(backtoApp) then it just starts all over again.
In other words, when setRoot(login); then setRoot(backToApp); <-- then AuthorizerService instance doesn't have its proper data set (such as loggedIn=true).
EDIT: Better Solution maybe:
main.js:
return aurelia.start().then(() => {
let Authorizer = aurelia.container.get(AuthorizerService);
let root = Authorizer.isAuthenticated() ? PLATFORM.moduleName('app') : PLATFORM.moduleName('login');
console.log('Current State: %o', Authorizer.auth);
aurelia.setRoot(root);
});
Authorizer.js
constructor(http) {
this.http = http;
this.auth = {
isAuthenticated: false,
user: {}
}
}
"this.auth" is no longer static. No longer "static auth = { isAuthenticated: false }" which was some example code I had found.
So now "auth" gets set inside "login" module. But this means the "login" module is displayed every single time the app loads briefly, before being redirected back to "setRoot(backToApp)"
If the class you want to get the instance is purely based on service classes and has no dependencies on some Aurelia plugins, it doesn't need to wait until Aurelia has started to safely invoke the container.
For your example:
aurelia.container.getInstance(AuthorizerService);
It can be
aurelia.container.get(AuthorizerService);
And you should not use new AuthorizerService(), as you have noticed in your question.