I maintain an app that has iOS and Android native versions, and now I and my pairs are considering migrate both to React Native. We want to do this step by step, so in the first moment, we consider that a good approach would be implementing the data layer into React Native and make the native apps consume the data through this hybrid layer. Is this possible? Sorry if this is a silly question, I never had any experience with React Native.
EDIT: The database would be primarily realm-js because we already have experience with Realm on Android - iOS uses Coredata and this is a reason for choosing this approach as well, have 2 database technology on the mobile side gave us some headaches already.
Here's an example on how to setup realm
import Realm from "realm";
// define your realm schemas
const TodoSchema: Realm.ObjectSchema = {
name: "todo",
primaryKey: "id",
properties: {
id: "int",
title: { type: "string", default: "untitled" },
body: { type: "string", default: "" },
isDone: { type: "bool", default: false},
},
};
interface ITodo {
title: string;
body: string;
isDone?: boolean;
}
class RealmDao {
private realmDao: Realm;
private static instance: RealmDao | null = null;
private constructor() {
this.realmDao = new Realm({
schema: [TodoSchema], // array of schemas
schemaVersion: 0,
});
}
// insuring that only one instance exists
public static getInstance = () => {
if (RealmDao.instance === null) {
RealmDao.instance = new RealmDao();
}
return RealmDao.instance;
};
public insert = (tableName: string, todo: ITodo) =>
this.realmDao.write(() => {
this.realmDao.create("todo", tableName);
});
};
public retrieveAll = (tableName: string) => {
return this.realmDao.objects(tableName);
};
}
const insert = (tableName: string, todo: ITodo) => {
RealmDao.getInstance().insert(tableName, todo);
}
const retrieveAll = (tableName: string) => {
RealmDao.getInstance().retrieveAll(tableName);
}
export { insert, retrieveAll };
Made it singleton to ensure that only one instance exists, I ran into a problem before where multiple instances of realm are running.
Now you can use the data in your component
import React from "react";
import { View, Text } from "react-native";
import { insert, retrieveAll } from "./realmDao";
const Example = () => {
React.useEffect(() => { insert({'title', 'something to be done'}); }, []);
return (
<View>
<Text>{JSON.stringify(retrieveAll ('todo'))}</Text>
</View>
)
);
Related
I am new to developing applications with react native using Realm databases, and am having trouble syncing the data though I followed the documentation. The application doesn't throw any errors but crashes whenever I run it, after I call the opening of the realm.
I created the schema from the SDK section in Realm and imported it, as well as copying the AppId.
Here is the code:
import React from 'react';
import { prescriptionSchema } from './Prescriptions'
import {
View,
Text,
TouchableOpacity
} from 'react-native';
import Realm from 'realm';
async function start() {
const appConfig = { id: 'AppId', timeout: 10000 }
console.log(appConfig)
const app = new Realm.App(appConfig);
console.log('created app')
const credentials = Realm.Credentials.anonymous();
console.log(credentials)
const user = await app.logIn(credentials);
console.log(user.id)
try {
console.log([prescriptionSchema])
const realm = await Realm.open({
schema: [prescriptionSchema],
sync: {
user: user,
partitionValue: 'name',
},
})
}
class App extends React.Component {
render() {
start();
return (
<View>
<TouchableOpacity>
<Text>temporary</Text></TouchableOpacity></View>
)
}
}
export default App
This is the schema:
export const prescriptionSchema = {
name: 'prescription',
properties: {
_id: 'objectId?',
time: 'string?',
day: 'string[]',
food: 'string?',
name: 'string?',
prescription: 'string?',
},
primaryKey: '_id',
};
If anyone can find where I went wrong or point me in the correct direction I would be very very grateful!
I am building an Android app using Ionic. And using the following feathers_client.js
const feathers = require('#feathersjs/feathers');
const socketio = require('#feathersjs/socketio-client');
const auth = require('#feathersjs/authentication-client');
const io = require('socket.io-client');
const socket = io('http://mydomain.example:3030');
const feathers_client = feathers();
feathers_client
.configure(socketio(socket))
.configure(auth({ storage: window.localStorage }));
module.exports = feathers_client;
When I run the app at the browser it works fine. But when I run it at an Android device I only get "NotAuthenticated".
I am assuming this is happening because FeathersJS stores the JWT token at window.localStorage and this is not available at the Android app userspace.
Two questions:
1) Is there any way to tell FeathersJS to store this token somewhere else?
2) If not, anyone faced this situation and may provide me a solution?
By the way, this is my code for authenticating:
export class SSHSettingsPage implements OnInit {
public inputEmail: string;
public inputPassword: string;
constructor() { }
ngOnInit() {
}
public performLogin($event) {
let authObj: object = { "strategy": "local", "email": this.inputEmail, "password": this.inputPassword};
client.authenticate(authObj)
.then(res => {
console.log(res);
window.localStorage.setItem("user",JSON.stringify(res.user));
window.location.href = "/download";
})
.catch(err => {
console.log(err);
window.location.href = "/login-error";
})
}
}
As mentioned in the configuration API the storage option can be passed an instance of the React Native AsyncStorage:
import {AsyncStorage} from 'react-native';
// Available options are listed in the "Options" section
app.configure(auth({
storage: AsyncStorage
}))
I am building an integrated app with Android native and React native communicating with each other.
For sending data from Native to React native I tried to pass data by using initial props but it was not working and showing undefined. Then I tried to use DeviceEventEmitter which kind of worked but there was a slight problem.
EDITED :
Here's the code snippet:
class Details extends Component {
constructor(props){
super(props);
this.state={data: '', id: '278'}
}
componentDidMount(){
const eventEmitter = new NativeEventEmitter();
this.subscription = eventEmitter.addListener('customEventName',(e: Event)=>{
this.setState({id: e.key1});
console.warn(this.state.id);
});
const API_key = "APIkey"
console.warn(this.state.id);
const URL = "https://api.themoviedb.org/3/movie/" + this.state.id + "?api_key=" + API_key + "&language=en-USs"
return fetch(URL, {
method: 'GET'
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
data: responseJson,
},
function(){
});
})
.catch((error) =>{
console.error(error);
});
}
componentWillUnmount(){
this.subscription.remove();
}
render() {
return(
/*something*/
);
}
}
The value of id is being successfully sent from native component to React native component.
Problem: The console.warn() inside addlistener() is showing after the console.warn() which is below declaring the API_key, and hence the this.state.id is not being updated.
Please any help will be appreciated.
your event register should be something as mentioned below, as you are registering the event so the scope of this should be event handler specific so if you want to access the parent scope you need to use the arrow function like mentioned below.
DeviceEventEmitter.addListener('customEventName',(e: Event)=> {
this.id = e.key1
console.warn("inside event emitter", this.id);
});
If you are successfully getting the event then I think this is just a React problem.
It looks like you want to fetch after you have successfully got the ID, but you're trying to fetch straight away in componentDidMount.
As fetch is a side effect you probably want to use componentDidUpdate like so:
import { NativeEventEmitter } from 'react-native'
constructor(props){
super(props);
this.state={
data: '',
id: ''
}
}
componentDidMount(){
const eventEmitter = new NativeEventEmitter();
this.subscription = eventEmitter.addListener('customEventName',(e: Event)=>{
this.setState({id: e.key1});
console.warn(this.state.id);
});
}
componentDidUpdate() {
const { id } = this.state
if (id) {
const URL = "https://api.themoviedb.org/3/movie/" + this.state.id + "?api_key=" + API_key + "&language=en-USs"
return fetch(URL, {
method: 'GET'
})
// ... etc
}
}
// ...etc
Note that id starts out as empty.
I'm dynamically creating components in create-react-native-app. Everything is working fine using the expo app for testing in Development mode using npm start, and connecting with an android phone.
If I switch it to Production mode, or try to build the apk as a Standalone app the object is not created on the Button press.
This is my first project with React Native, and I don't know how to debug this.
I've also been unable to find any information about what the differences between these two modes might be that would lead to this.
Here the relevant code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.updateState = this.updateState.bind(this);
this.state = {
knobs: [],
key: 1
}
}
add = () => {
let key = this.state.key + 1
let knob = (<Object key={key} updateState={this.updateState}/>);
let knobs = this.state.knobs;
knobs.push(knob);
this.setState({knobs: knobs, key: key})
}
render = () => {
return ([<View>
{this.state.knobs}
<Button onPress={() => this.add()} title='add thing'/>
</View>
]);
}
}
I'm not sure what causes the issue since we don't have any sort of error message but below snippet of code might help.
When you assign a variable like below;
let knobs = this.state.knobs;
You are not creating a new variable, you are creating a reference to the original property. Because of this you mutate the state. This might cause the issue.
For setting new state values related to current state values you can use functional setState syntax and destructuring assignment. It is a little bit more easy to use and a little bit more easy to read.
add = () => {
this.setState((prevState) => {
const { knobs, key } = prevState; // deconstruct array and key from state
const newKnob = (<Object key={(key + 1)} updateState={this.updateState}/>);
knobs.push(newKnob); // push new item to array
return { knobs, key: (key + 1) } //return new state values
});
}
Oh, so in the end I rewrote the whole bit.
Moving the objects to be created into the render function.
export default class App extends React.Component {
constructor() {
super();
this.state = {
things: []
}
this.key = 0;
}
add = () => {
let addThing = { key: this.key }
this.setState({ things: [ ...this.state.things, addThing ] })
this.key = this.key + 1;
}
render() {
let newThings = this.state.things.map((key) => {
return (
<Text key={key}>New Thing.</Text>
);
});
return (<View style={styles.container}>
{newThings}
<Button onPress={() => this.add()} title='add thing'/>
</View>);
}
}
This functions as expected in Production mode and as an App;)
Fairly new to React native and its concepts. I have been playing with RN for a while to create an application to fetch API data from
http://jsonplaceholder.typicode.com/photos
I have been looking into the documentation of AsyncStorage to implement how i can cache the API data so that upon terminating the application, it doesn't have to deal with fetching the data from web again and again, but wasn't successfully able to implement it.
It will be great if you can provide me help/suggestion based on it. I have included my source code for the 2 important files in my application, along with the a Test.js file with how i was trying to achieve.
import React, {Component} from 'react';
import { FlatList, View, Text, AsyncStorage, ActivityIndicator } from 'react-native';
import axios from 'axios';
import GalleryDetail from './GalleryDetail';
class GalleryList extends Component {
state = { photos: []};
componentDidMount() {
axios.get('http://jsonplaceholder.typicode.com/photos')
.then(response => this.setState({ photos: response.data }))
.catch((error)=> console.warn("fetch Error: ", error));
}
getPhotos = async()=> {
try {
photos = await AsyncStorage.getItem('GalleryPhotos');
}
catch (error) {
console.error(error);
}
}
savePhotos(){
AsyncStorage.setItem('GalleryPhotos', this.state.photos);
console.log('works !');
}
renderPhoto = ({item})=> {
return <GalleryDetail photo={item}/>
}
keyExtractor = (photo, index) => photo.id;
render () {
if(!this.state.photos){
return <ActivityIndicator/>;
}
return (
<FlatList
data = {this.state.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
export default GalleryList;
and GalleryDetail linked with GalleryList-
import React, {Component} from 'react';
import { Text, View, Image } from 'react-native';
import Card from './Card';
import CardSection from './CardSection';
const GalleryDetail = (props)=> {
return (
<Card>
<CardSection style = {styles.headerContentStyle}>
<Image
style={styles.thumbnailStyle}
source = {{ uri: props.photo.thumbnailUrl}}/>
<Text style= {styles.textStyle}>{props.photo.title} </Text>
</CardSection>
</Card>
);
};
const styles = {
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
thumbnailStyle: {
height: 60,
width: 60
},
textStyle: {
fontSize: 12,
//textAlign: 'right',
flexDirection: 'row',
justifyContent: 'flex-end',
flex: 1,
flexWrap: 'wrap',
marginLeft: 5,
marginRight: 5,
}
}
export default GalleryDetail;
My method of trying was that-
Upon launching the application, it will first look in asyncStorage, if it finds the data- it fetches from async otherwise going to the web,fetching and storing again for later use.
I tried to implement somewhat like this in a separate file since i dint wanted to breakdown my already running app. The weird broken syntax is
State = {
photos: []
}
componentDidMount() {
// just a variable acting to fetch data from the stored keyvalue pair
check = AsyncStorage.getItem("PhotosKey").then((response) => {
this.setState({"PhotosKey": response});
}).done();
if(check) {
console.log('Data was fetched!!!!!');
check();
}
else {
console.log("Data was not fetched!");
var Data = axios.get('http://jsonplaceholder.typicode.com/photos').
then(response => this.setState({ photos: response.data })).
catch((error)=> console.warn("fetch Error: ", error));
}
}
Thanks in advance!
async componentDidMount() {
const photoStorage = await AsyncStorage.getItem('GalleryPhotos')
if(photoStorage) {
try {
const photoResp = await axios.get('http://jsonplaceholder.typicode.com/photos')
const photoData = await JSON.stringify(photoResp.data)
await AsyncStorage.setItem('GalleryPhotos', photoData);
} catch(e) {
console.warn("fetch Error: ", error)
}
.then(response => this.setState({ photos: response.data }))
}
}
later
getPhotos = async()=> {
try {
photos = JSON.parse(await AsyncStorage.getItem('GalleryPhotos'));
}
catch (error) {
console.error(error);
}
}
The approach from Subramanya is basically all you need to get started, I'm just going to introduce a state management approach with redux-persist where you can definitely appreciate when your app grows.
Redux Persist is performant, easy to implement, and easy to extend.
Let say you have your app hooked up with redux and implemented a fairly organised state tree, redux-persist stores the entire app state with AsyncStorage or any storage engine of your choice.
For instance, let's assume that your API endpoint returns a collection of photos, all you need to do is update the store, and your users can expect their data is safe and saved with redux-persist.
I have not tested all the code below
Let's define the store first,
import { AsyncStorage } from 'react-native';
import { createStore, compose, applyMiddleware, } from "redux";
import { persistStore } from "redux-persist";
import ReduxThunk from "redux-thunk";
import reducers from "../reducers"
const middleWare = [ReduxThunk]
const store = createStore(
reducers,
{},
compose(applyMiddleware(...middleWare))
)
// you can define more parameters, like blacklist or whitelist a reducer
// also, specify storage engine
persistStore(store, { storage: AsyncStorage });
export default store;
At your app's entry point,
import React, { Component } from "react";
import { Provider } from "react-redux";
import Router from "./Router";
import store from './store';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<Router /> // navigator
</Provider>
);
}
}
Finally, your API logic.
// action creator
export storePhoto = photos => {
return {
type: 'STORE_PHOTOS',
payload: photos
}
}
// photos reducer
import { REHYDRATE } from 'redux-persist/constants';
export default (state = {}, action) => {
switch (action.type) {
case STORE_PHOTOS:
return { ...state, photos: action.payload }
// this is where `redux-persist` handles caching
case REHYDRATE:
var incoming = action.payload;
if(incoming) return { ...state, ...incoming }
return state;
default:
return state;
}
};
To retrieve data, you will see that redux abstracts away all the excess logics and there is no more setItem, getItem because redux-persist does that automagically for your already.
import { connect } from "react-redux";
import { storePhotos } from "./actions";
class GalleryList extends Component {
async componentDidMount() {
const photos = await axios.get('http://jsonplaceholder.typicode.com/photos');
storePhoto(photos)
}
renderPhoto = ({ item }) => <GalleryDetail photo={item}/>
keyExtractor = (photo, index) => photo.id;
render () {
return (
<FlatList
data = {this.props.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
// pull data from photos reducer
const mapStateToProps = ({ photos }) => {
return {
photos: photos.photos
}
}
export default connect(mapStateToProps, { storePhotos })(GalleryList);
To summarise,
Install redux-persist in you project.
Import persistStore and autoRehydrate form redux-persist.
Add autoRehydrate to your store.
Pass your store to persistStore.
Listen to the persist/REHYDRATE action on your reducer and populate state accordingly.
Hope my answer helps!
Answer
Caching the data for a specific period of time
const cacheIntervaInHours = 24
const cacheExpiryTime = new Date()
cacheExpiryTime.setHours(cacheExpiryTime.getHours() + cacheIntervalInHours)
const lastRequest = await AsyncStorage.getItem("lastRequest")
if (lastRequest == null || lastRequest > cacheExpiryTime) {
fetch(`${apiUrl}/blogPosts/recent`)
.then(async (response) => {
return await response.json()
})
.then(async (json) => {
if (!json || json.length == 0) {
throw new Error()
}
AsyncStorage.setItem("lastRequest", new Date());
return await AsyncStorage.setItem('blogPosts', JSON.stringify(json))
})
.catch(error => {
console.error(error)
})
}