Skip to content

Instantly share code, notes, and snippets.

@dereknelson
Last active July 11, 2019 21:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dereknelson/cfd249dea35014fef540f4203b96b6e3 to your computer and use it in GitHub Desktop.
Save dereknelson/cfd249dea35014fef540f4203b96b6e3 to your computer and use it in GitHub Desktop.
Entirety of image caching
import produce from 'immer'
import moment from 'moment'
import { DOWNLOADING, DOWNLOAD_IMAGE_SUCCESS, PURGE_IMAGE_CACHE } from './NotDumbImageActions';
const initialState = {
loaded: {},
downloading: [],
purgedCache: false
}
export const imgDownloadIsOld = (startedDownloading) => moment().diff(moment(startedDownloading), 'seconds') > 15
export default (state = initialState, action) => {
switch (action.type){
case DOWNLOADING: {
const downloading = produce(state.downloading, newList => {
newList.push(action.payload)
newList = newList.filter(img => !imgDownloadIsOld(img.startedDownloading))
})
return { ...state, downloading }
}
case DOWNLOAD_IMAGE_SUCCESS: {
let downloading = state.downloading.filter((img) => img.source != action.source)
let loaded = { ...state.loaded, [action.source]: action.local }
return { ...state, downloading, loaded }
}
case PURGE_IMAGE_CACHE:
return { ...initialState, purgedCache: true }
default: return state
}
}
import React, { Component, PureComponent } from 'react';
import { connect } from 'react-redux';
import { View, ActivityIndicator, Image, Platform } from 'react-native';
import { FileSystem } from 'expo'
import md5 from 'js-md5';
const ios = Platform.OS == 'ios'
import { downloadImage, removeFromDownloading } from './NotDumbImageActions';
import { imgDownloadIsOld } from './cacheRedux';
export const documentFolder = `${FileSystem.documentDirectory}yourDocumentFolder`; //documentDirectory ends in a /
/* in my root component App.js I have the following lines:
async componentWillMount(){
let folderInfo = await FileSystem.getInfoAsync(documentFolder)
if (!folderInfo.exists) await FileSystem.makeDirectoryAsync(documentFolder)
}
*/
const findDownloading = (downloading, uri) => downloading.filter(img => img.source == uri).length > 0
@connect((state, props) => {
const { downloading, loaded } = state.cache, isDownloading = findDownloading(downloading, props.source.uri)
return {
isDownloading,
downloadingList: downloading,
local: loaded[props.source.uri],
downloadingThisMany: downloading.length
}
}, { downloadImage, removeFromDownloading })
export default class NotDumbImage extends PureComponent {
constructor(props){
super(props)
this.fetchImage = this.fetchImage.bind(this), this.cacheTimer = this.cacheTimer.bind(this), this.setFetched = this.setFetched.bind(this)
this.state = { fetched: false }
}
fetchImage() {
const { local, isDownloading, dontCache, downloadImage, source, downloadingList: dlList } = this.props
let index = dlList.findIndex(img => img.source == source.uri), shouldDownloadAnyway = false
if (index != -1) shouldDownloadAnyway = imgDownloadIsOld(dlList[index].startedDownloading) && dlList.filter(img => !imgDownloadIsOld(img.startedDownloading)).length == 0
if ((!local && !isDownloading && !dontCache) || (!local && shouldDownloadAnyway)) downloadImage(source)
}
componentDidUpdate(prevProps) {
if (prevProps.source.uri != this.props.source.uri) this.fetchImage()
}
async componentDidMount(){
this.fetchImage()
}
cacheTimer() {
this.timer = setTimeout(() => this.fetchImage(), 16000)
}
setFetched = () => this.setState({ fetched: true })
render() {
const { local, style, source, resizeMode, list, dontCache } = this.props, { fetched } = this.state
const hash = md5(source.uri), split = source.uri.split('.'), type = split[split.length - 1],
file = dontCache ? source.uri || '' : `${documentFolder}/${hash}.${type}`;
if (!local || source && (typeof source.uri != 'string' || source.uri == '')) {
this.cacheTimer()
return (
<View style={[style, { backgroundColor: 'gray', justifyContent: 'center' }]} >
<ActivityIndicator animating={true} size="small" />
</View>
)
}
clearTimeout(this.timer)
//default source prevents flickering on setstate but fucks up the performance of flatlists for some reason
if (Platform.OS == 'ios' && !__DEV__ && !list) return <Image style={style} onLoadEnd={this.setFetched} resizeMode={resizeMode} defaultSource={{ uri: file }} />
else return <Image style={style || defaultStyle} onLoad={this.setFetched} source={{ uri: file }} resizeMode={resizeMode} />
}
}
const defaultStyle = { height: 50, width: 50, borderRadius: 25 }
import md5 from 'js-md5';
import { FileSystem } from 'expo'
import moment from 'moment'
export const DOWNLOADING = 'DOWNLOADING'
export const DOWNLOAD_IMAGE_SUCCESS = 'DOWNLOAD_IMAGE_SUCCESS'
export const PURGE_IMAGE_CACHE = 'PURGE_IMAGE_CACHE'
import { documentFolder } from './NotDumbImage';
export function downloadImage(theSource){
return async (dispatch, getState) => {
let source = theSource.uri
if (!source || source == '') return
dispatch(downloading(source))
const hash = md5(source), split = source.split('.'), type = split[split.length - 1]
const output = `${documentFolder}/${hash}.${type}`;
const downloaded = await FileSystem.downloadAsync(source, output)
return dispatch(downloadImageSuccess(downloaded, source))
}
}
function downloading(source){
return { type: DOWNLOADING, payload: { source, startedDownloading: moment() } }
}
function downloadImageSuccess(downloaded, source){
return { type: DOWNLOAD_IMAGE_SUCCESS, local: downloaded.uri, source }
}
export function purgeCache(){
return { type: PURGE_IMAGE_CACHE }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment