I am wanting to develop an app where a user can select different gamepacks to install on their android or ios device. A gamepack will consist of:
one or more JSON files
images
sounds
Right now I'm not really concerned if these are transferred individually or in a zip file (though a zip file would certainly be preferred). Of course the device will need to be connected to the Internet to get the current list of gamepacks and to download the ones that the user chooses. Once the gamepacks are downloaded to the phone the user will not need an Internet connection to play the game as they will have all the files.
How do I go about implementing this in my project?
How do I download the files? Would I use the react-native-fetch-blob library and save it in a specific location? This library refers to saving it as "cache" rather than permanently, so I am not sure if this is the correct solution. The specific section I am looking at on the library page is "Use Specific File Path". But because it is cache, should I be looking for something else that is more of a longer term storage? It does say on the page that files will not be deleted so I am a bit confused as to what the difference between permanent storage and cache is in this case.
Once the files are downloaded would I then be able to open images and display them, open sound files and play them, and open the json files and process them?
Check out React Native FS, specifically the documentation on downloadFile:
https://github.com/johanneslumpe/react-native-fs#downloadfileoptions-downloadfileoptions--jobid-number-promise-promisedownloadresult-
Here's a working example:
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View,
Image,
} from 'react-native';
import RNFS from 'react-native-fs';
export default class downloadFile extends Component {
constructor() {
super()
this.state = {
isDone: false,
};
this.onDownloadImagePress = this.onDownloadImagePress.bind(this);
}
onDownloadImagePress() {
RNFS.downloadFile({
fromUrl: 'https://facebook.github.io/react-native/img/header_logo.png',
toFile: `${RNFS.DocumentDirectoryPath}/react-native.png`,
}).promise.then((r) => {
this.setState({ isDone: true })
});
}
render() {
const preview = this.state.isDone ? (<View>
<Image style={{
width: 100,
height: 100,
backgroundColor: 'black',
}}
source={{
uri: `file://${RNFS.DocumentDirectoryPath}/react-native.png`,
scale: 1
}}
/>
<Text>{`file://${RNFS.DocumentDirectoryPath}/react-native.png`}</Text>
</View>
) : null;
return (
<View>
<Text onPress={this.onDownloadImagePress}>Download Image</Text>
{preview}
</View>
);
}
}
AppRegistry.registerComponent('downloadFile', () => downloadFile);
It's important to know that the height and width must be set on the Image
Related
I have a very old react native application written on Expo using SDK version 32. Unfortunately, we don't have the time and resource to migrate to the latest version of the SDK (40).
So, right now, we struggle with an issue which reproduces on an Android. One of our screens has a background video (implemented with Expo Video component) and two buttons over it.
import React from 'react';
import { TouchableOpacity, StyleSheet, View, Text, Animated, Dimensions } from 'react-native';
import { AppLoading, Asset, Video } from 'expo';
const defaultVideoSource = require('../content/videos/be.mp4');
export default class HomeScreen extends React.Component {
static navigationOptions = {
header: null,
};
constructor(props) {
super(props);
this.state = {
backgroundOpacity: new Animated.Value(0),
loaded: false,
videoHeight: height,
videoWidth: width,
};
}
async componentWillMount() { }
// this is called from the video::onLoad()
fadeInVideo = () => {
const { backgroundOpacity } = this.state;
setTimeout(() => {
Animated.spring(backgroundOpacity, {
toValue: 1
}).start();
}, 400);
};
render() {
const { backgroundOpacity, loaded, videoHeight, videoWidth } = this.state;
if (!loaded) { return <AppLoading />; }
return (
<View style={styles.container}>
<View style={styles.background}>
<Animated.View
style={[
styles.backgroundViewWrapper,
{ opacity: backgroundOpacity }
]}
>
<Video
isLooping
isMuted={false}
onLoad={() => this.fadeInVideo()}
resizeMode="cover"
shouldPlay
source= { defaultVideoSource }
style={{ height: videoHeight, width: videoWidth }}
/>
</Animated.View>
</View>
<View style={styles.overlay}>
// BUTTONS OVER THE VIDEO
</View>
</View>
);
}
}
const styles = StyleSheet.create({
// ...
});
but for some reason, when I publish it to my Expo account and run it from there the video freezes at a certain moment and stops playing.
The buttons over the video are still active and functional. Like nothing happened but I need to restart the application (via Expo client app on Android) in order to start playing again. I have noticed that If I start it in a debug mode with the command:
expo start
and scan the QR code from the Expo client app there's no freezing of the video. So, for that reason, I would like to create a standalone file (.apk) which I can execute directly on my device without the need of using Expo client app. But, the problem here is that when I try to use the command:
expo build:android
I get an error saying:
Unsupported SDK version: our app builders no longer support SDK
version 32. Please, upgrade to at least SDK 36.
But, as I said, we currently don't have the time to do that because this (SDK update) would affect other components of our app that will need to be rewritten and retested. So, my question finally: Is there any alternative for building an .apk while using SDK 32.0?
It's possible to build apps that use old expo sdks, but I'm not sure if play store will accept those old versions, there was a lot of changes around privacy policy and terms of service plus at certain point google started to require 64 bit binaries(I'm not sure if sdk 32 already had those changes). If that's the case your only option is to upgrade to supported SDK.
If you want to build that app, you can do that with turtle-cli https://www.npmjs.com/package/turtle-cli. This tools executes locally the same code that runs on expo servers, but it's not fully integrated with expo infrastructure, so you will need to provide keystore and all the passwords manually in the cli and either your expo credentials(if you use expo publish) or url to the js bundle (if you self-host). You will need to use old enough version of turtle-cli that still have that sdk, you can consult CHANGELOG here https://github.com/expo/turtle/blob/master/CHANGELOG.md to check that.
I am trying to implement a Document Picker using Expo v32. The component opens, closes and uploads properly. However, Android's document picker only allows me to choose images.
I have tried specifying the MIME types for files: application/msword, application/vnd.ms-excel for example. But this still doesn't work. I also tried setting { type: "*/*" } in the getDocumentAsync call but with no luck.
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { DocumentPicker } from 'expo';
import { TouchableOpacity, Text } from 'react-native'
import { postDocument } from '../../../actions/deal'
class AddFiles extends Component {
_pickDocument = async () => {
// Get the document from picker and upload with API
let result = await DocumentPicker.getDocumentAsync({});
if(result.type == 'success'){
this.props.postDocument(this.props.dealId, result)
}
}
render(){
return(
<TouchableOpacity onPress={this._pickDocument}>
<Text>
Add Files
</Text>
</TouchableOpacity>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ postDocument }, dispatch)
}
export default connect(null, mapDispatchToProps)(AddFiles)
I would expect to see all possible files available for choosing, but I can only pick images.
Any idea on what I'm missing?
So this code actually works but there is one catch:
Expo's Document Picker - by default - does not allow Google Docs, Sheets or Slides to be added.
All of those files which are "disabled" are Google Drive files. Once you convert them to proper .docx or .xls then they will work.
To add google drive files, you need to specify their MIME types:
application/vnd.google-apps.document
application/vnd.google-apps.spreadsheet
application/vnd.google-apps.presentation
All Google Drive Mime types are here: https://developers.google.com/drive/api/v3/mime-types
I have created an app and added splach screen. It takes 1 second to load the app on Android Emulator. However after I publish the app in the store, it takes 4 seconds to load.
This is pretty annoying for such a simple app.
I was thinking it was because of _loadResourcesAsync func to load pictures. Therefore I commented out those lines but nothing has changed.
Any recommendation to speed up my app launch.
Here you can find my app.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset } from 'expo';
import AppNavigator from './navigation/AppNavigator';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoadingComplete: false,
};
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</View>
);
}
}
_loadResourcesAsync = async () => {
return Promise.all([
Asset.loadAsync([
// require('./assets/images/big_bottle.png'),
// require('./assets/images/bottle.png'),
// require('./assets/images/coffee.png'),
// require('./assets/images/juice.png'),
// require('./assets/images/menu.png'),
// require('./assets/images/tea.png'),
// require('./assets/images/water-glass.png'),
]),
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
Based on my experience, hybrid apps, in general, tend to launch slower.
Based on my tests, even a completely blank Expo app, based on the device performance, can take anywhere from 1-4s to launch. This is the time it takes from the moment the user taps (opens) the app to the time the splash screen gets hidden (and the first app screen is visible).
What you can do to speed it up is a really broad topic. Here are two recommendations, that helped a lot for a project of mine:
Cashe your assets.
Bundling assets into your binary will provide for the best user experience as your assets will be available immediately. Instead of having to make a network request to the CDN to fetch your published assets, your app will fetch them from the local disk resulting in a faster, more efficient loading experience.
Always load a cached version of your app first.
You can configure the "updates" section in your app.json to always starts with the cached version of your app first (fallbackToCacheTimeout: 0) and continue trying to fetch the update in the background (at which point it will be saved into the cache for the next app load).
Unfortunately, as of today, it looks like we're kind of limited to what we can do to improve further the initial loading time on a blank app. There isn't really a reliable way to know how long the 'check for updates' process takes, the general React Native initialization time, the 'load all the JS' time.
There is a feature request for tools for improving the startup time, would be great if the Expo team introduces something similar anytime in the future.
Is there a good library or maybe some default react native components that cache the image from a url?
I've tried react-native-cache-image but there are a lot of issues with react-native-fs and react-native-sqlite-storage and as I am new to react native I dont know how to fix them properly.
yarn add react-native-fast-image
Ref: https://github.com/DylanVann/react-native-fast-image
import FastImage from 'react-native-fast-image'
<FastImage
style={{ width: 200, height: 200 }}
source={{ uri: 'https://unsplash.it/400/400?image=1' }}
resizeMode={FastImage.resizeMode.stretch}
/>
You may be interested in my higher order component module that adds performance related image caching and "permanent cache" functionality to the native <Image> component. My module depends on react-native-fetch-blob which is the goto well-respected and battle-tested library for downloading files, so you shouldn't have dependency problems.
React Native Image Cache HOC
Tl;DR Code Example:
import imageCacheHoc from 'react-native-image-cache-hoc';
const CacheableImage = imageCacheHoc(Image);
export default class App extends Component<{}> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<CacheableImage style={styles.image} source={{uri: 'https://i.redd.it/rc29s4bz61uz.png'}} />
<CacheableImage style={styles.image} source={{uri: 'https://i.redd.it/hhhim0kc5swz.jpg'}} permanent={true} />
</View>
);
}
}
The first image will be cached until the total local cache grows past 15 MB (by default) then cached images are deleted oldest first until total cache is below 15 MB again.
The second image will be stored to local disk permanently. People use this as a drop in replacement for shipping static image files with your app.
It also sounds like you are interested in arbitrarily storing image files to local disk. You can do that with a CacheableImage static method like so:
import imageCacheHoc from 'react-native-image-cache-hoc';
const CacheableImage = imageCacheHoc(Image);
CacheableImage.cacheFile('https://i.redd.it/hhhim0kc5swz.jpg', true)
.then( localFileInfo => {
console.log(localFileInfo);
// Console log outputs:
//{
// url: 'https://i.redd.it/rc29s4bz61uz.png',
// cacheType: 'permanent',
// localFilePath: '/this/is/absolute/path/to/file.jpg'
//}
});
Hope this helps!
Straight from the Expo docs:
import React from 'react';
import Expo from 'expo';
import { Image, View } from 'react-native';
import logo from './assets/icon.png';
const cacheImages = images => images.map(image => {
if (typeof image === 'string') return Image.prefetch(image);
return Expo.Asset.fromModule(image).downloadAsync();
});
class View extends React.component {
state = {
appIsReady: false
}
componentWillMount() {
this.loadAssetsAsync();
}
async loadAssetsAsync = () => {
const imageAssets = cacheImages([icon]);
await Promise.all([...imageAssets]);
this.setState({ appIsReady: true });
}
render() {
return(
<View>
<Image source={logo} style={styles.imageStyle} />
</View>
);
}
}
I think the code snippet is straight forward. You will have a state that will set to true when all your images are loaded. You will have the cacheImages function that will handle the work for you. The only requirement you will need is Expo.
I have used this library and working in both android and ios phones. It is working in Both EXPO and ReactNative. In react native automatically stored catch images.
For Installation the library:
yarn add picache
then use in your js file like this, first import the file and used it. For more information click
import Picache from "picache";
const App = () => (
<Picache
style={{ height: 150, width: 350 }}
source={require("./square.png")}
/>
);
There's prefetch() method built in Image component.
I'm just trying to load an image as a static resource. Here is an example that gives me a blank white screen.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
Image,
View
} = React;
var impowerz = React.createClass({
displayName : "Login",
render: function() {
return (
<View style={styles.container}>
<Image source={require('image!bg-splash')} style={styles.bg} />
</View>
);
}
});
var styles = StyleSheet.create({
bg : {
height: 667,
width: 375
},
container: {
flex: 1,
},
});
AppRegistry.registerComponent('impowerz', () => impowerz);
I checked in Chrome Debugger and this is what I got
[![Chrome Debugger sees my static resource][1]][1]
Edit I wrapped the image tag in a view and added a flex: 1 styles for the container
Update I tried to console.log() the assetsRoot property in react-native/packager/packager.js and it came back as /impowerz/assets/ which is where my image lives. I made a #2x and #3x as suggested [in this answer][2]
I tried using .jpg instead of .png images and tried replacing require('image!bg_splash') with {uri: 'assets/image/bg_splash.jpg'} (with and without the ext).
How did you add "bg-splash" to your android app? It should go under android/app/src/main/res/drawable (or drawable-hdpi, -xhdp etc if you have different version per DPI). Also take into account that android only accept resources names that contain lowercase letters and underscores, so your image name doesn't match that requirement as it contains dash sign.
in react-native v.0.14+
// same syntax for IOS and Android
require('./my-icon.png')
Full Image docs here
As of 0.14 release, React Native provides a unified way of managing images in your iOS and Android apps. To add a static image to your app, place it somewhere in your source code tree and reference it like this:
<Image source={require('./my-icon.png')} />
//relative path
.
├── button.js
└── img
├── check#2x.png
└── check#3x.png
I couldn't get my images to show up either.
When I went to <yourapp>/android/app/src/main/res, there were only mipmap-<qualifier> folders. I first put the images in those folders. Didn't work. My reading comprehension skills obviously need upgrading, because in the instructions, it clearly says "drawable." Then, I made the mistake of thinking that these "mipmap" folders needed to be changed to "drawable" folders (in the place of "mipmap"). So if anyone did that, backtrack, and instead, add the drawable-<qualifier> folders in addition to the "mipmap" ones. And then make sure to recompile the app.
To get the image to show on the screen, put the image in a view:
return (
<View
style={styles.container}>
<Image source={require('image!bg-splash')} style={styles.bg} />
</View>
);
And add these styles to the view
var styles = StyleSheet.create({
container: {
flex: 1,
},