Hot questions for Using Lottie in react native

Top 10 Java Open Source / Lottie / react native

Question:

I want to navigate a splash screen to next screen after certain timeout. Splash screen have an animation, done with the help of Airbnb Lottie for React Native.

The splashscreen code goes as follows:

import React from "react";
import { Animated, Easing } from "react-native";
import LottieView from "lottie-react-native";
import { NavigationActions } from "react-navigation";

export default class SplashScreen extends React.Component {
  static navigationOptions = {
    header: null
  };

  constructor() {
    super();
    this.state = {
      progress: new Animated.Value(0),
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.navigateToWalkthrough()
    }, 3500);
    
    Animated.timing(this.state.progress, {
      toValue: 1,
      duration: 3000,
      easing: Easing.linear,
    }).start();
  }

  navigateToWalkthrough = () => {
    const navigateAction = NavigationActions.reset({
      index: 0,
      actions: [NavigationActions.navigate({ routeName: "Walkthrough" })],
    });

    this.props.navigation.dispatch(navigateAction);
  }

  render() {
    return(
      <LottieView 
      source={require("../assets/splash/SplashScreenAnimation.json")}
      progress={this.state.progress}
      />
    );
  }
}

Answer:

reset action is removed from NavigationActions and there is StackActions specific to StackNavigator in v2 of react-navigation.

StackActions is an object containing methods for generating actions specific to stack-based navigators. Its methods expand upon the actions available in NavigationActions.

The following actions are supported:

Reset - Replace current state with a new state

Replace - Replace a route at a given key with another route

Push - Add a route on the top of the stack, and navigate forward to it

Pop - Navigate back to previous routes

PopToTop - Navigate to the top route of the stack, dismissing all other routes

Question:

Context

I am new to lottie-react-native and have managed to implement my first animation:

constructor(props) {
    super(props);
    this.state = {
        progress: new Animated.Value(0),
        loop: true
    }
}
componentDidMount() {
    this.animation.play();
}
render() {
const { progress, loop } = this.state;
return (
    <View style={{display:'flex',height:'auto', alignItems: 'center',justifyContent:'center'}}>
    <LottieView
    ref={animation => {
        this.animation = animation;
      }}
    speed={1}
    autoPlay
    source={NOACTIVITY}
    progress={progress}
    loop={loop}
    height={300}
    width={300}
    style={{margin:0,}}
  />
  </View>
)

}

The Problem

I am now trying to create a loop with this animation that plays it forwards, then plays it backwards and then starts the process again.

I have done some research and concluded that this must be completed using the animated values and timing? I have found many examples (in the react native docs!) of playing forwards and backwards but not together.

Can this be completed on component did mount? or does it have to be a separate function?

Thanks in advance!


Answer:

The solution I came up with was using a sequence inside a loop as follows:

AnimateFunction = () => {
    Animated.loop(
        Animated.sequence([
            Animated.timing(
                this.state.progress,
                {
                  toValue: 1,
                  duration: (5000),
                  //easing: Easing.linear()
                }
              ),
              Animated.timing(
                this.state.progress,
                {
                  toValue: 0,
                  duration: (5000),
                  //easing: Easing.linear()
                }
              )
        ])

    ).start();
  }

I found that adding easing made the animation jump a little when the application restarted at 0 so it is commented out for now.

Question:

I'm using React Native Lottie Wrapper to show animation on screen. I need a functionality to play/pause/resume animation.

Here is my a part of my code:

...

constructor(props) {
  super(props);
  this.state = {
    progress: new Animated.Value(0)
  };
}

static navigationOptions = {
  title: "Details",
  headerStyle: {
    backgroundColor: '#f4511e',
  },
  headerTintColor: '#fff',
  headerTitleStyle: {
    fontWeight: 'bold',
  },
  headerTruncatedBackTitle: 'List'
};

componentDidMount() {
  this.animation.play();
}

playLottie() {
 console.log('play');
}

pauseLottie() {
  console.log('pause');
}

render() {
  return (
    <View>
      <Animation
        ref={animation => { this.animation = animation; }}
        source={require('../../../../assets/anim/balloons.json')}
        style={{height: 300, width: '100%'}}
        loop={false}
        progress={this.state.progress}
      />
      <Text>Course with id: {this.props.navigation.state.params.courseId}</Text>
        <Button 
          onPress={this.playLottie}
          title="Play Lottie"
          color="#841584"
          accessibilityLabel="Play video"
        />
        <Button 
          onPress={this.pauseLottie}
          title="Pause Lottie"
          color="#841584"
          accessibilityLabel="Pause video"
        />
     </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

...

The animation is playing well but I can't pause it and resume it. Does anyone have a solution for this problem?

P.S. I have tried to use this.animation in pauseLottie() method but it said that is undefined.

Thank you in advance!


Answer:

You have to set the state from the play/pause functions. In order to access the state of the Component, you have to bind the function to the component class:

First option in your constructor:

constructor(props) {
  super(props);
  this.playLottie.bind(this);
  this.pauseLottie.bind(this);
}

or second option when declaring inside class use the es6 function syntax:

playLottie = () => {
 ...
}

pauseLottie = () => {
 ...
}

Inside those function call setState and add the value you want to set it to. In your case I would:

playLottie = () => {
  this.setState({ progress: true })
}

pauseLottie = () => {
  this.setState({ progress: false })
}

It is important you bind those two functions to your class component, because you will not be able to access component props. Thats why it is throwing you an error setState is not a function

Your render looks good ;)

Question:

Say if my animation is being iterated 20 times, but I don't want all of them playing at the same time. How would I go about triggering that one specific animation that have a value of 1. If I clicked on 1 then the other 19 should not trigger except that 1.

    export default class AnimateScreen extends React.PureComponent {
    constructor(props){

      super(props);

      this.forceUpdateHandler = this.forceUpdateHandler.bind(this);

      this.state = {
      dataSource: '',
      progress: new Animated.Value(0),
    };


    animate = (id) => {

          Animated.timing(this.state.progress, {
            toValue: 1,
            duration: 5000,
            easing: Easing.linear,
          }).start([id]); <!-- Here is my attempt in trying to animate that one specific animation. 


render(){
return(
    <FlatList
               data={this.state.dataSource}
               renderItem={({item}) => 
              <View>
               <View>
                 <Text>Work in progress</Text>
                 <View>
                 <TouchableHighlight
                 onPress={this.animate.bind(this, item.id)}>
                  <Animation
                  progress={this.state.progress}
                  source={require('../tools/animations/heart_icon.json')}
                  />
        </TouchableHighlight>

                   <Text> Hello</Text>

                 </View>

               </View>


               </View>
             }
             keyExtractor={(item, index) => index.toString()}
          />
        );
        }
    }

I tried this out and still, all of the animations triggered. Is there a way to trigger them specifically?

DataSource:

"dataSource":[{"id":"10","images":"Emerson live in the sunshine swim in the sea drink the wild air.jpg","note":"Hello","tag":"sunshine"}


    componentDidUpdate(prevProps, prevState) {
  if (!prevState.dataSource) {

      return fetch(`https://www.website.com/React/json-data.php` , {
       method: 'POST',
       headers: {
         'Accept': 'application/json',
         'Content-Type': 'application/json',
       }

      })
        .then((response) => response.json())
        .then((responseJson) => {
          this.setState({
            dataSource: responseJson,
            },function() {
              // In this block you can do something with new state.
            });
        })
        .catch((error) => {
          console.error(error);
        });
    }
  }

Answer:

Basically you can add progress key to each item in the dataSource with an animated value to 0 then on click you will animate that item's progress. It should roughly look like this:

export default class AnimateScreen extends React.PureComponent {
  constructor(props){

    super(props);

    this.forceUpdateHandler = this.forceUpdateHandler.bind(this);

    this.state = {
      dataSource: [],
      progress: new Animated.Value(0),
    };

    componentDidUpdate(prevProps, prevState) {
      if (!prevState.dataSource) {

        return fetch(`https://www.website.com/React/json-data.php` , {
          method: 'POST',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
          }

        })
        .then((response) => response.json())
        .then((responseJson) => {
          this.setState({
            dataSource: responseJson.map(item => ({ ...item, progress: new Animated.Value(0) })), // Add progress key
          },function() {
            // In this block you can do something with new state.
          });
        })
        .catch((error) => {
          console.error(error);
        });
      }
    }


    animate = (item) => {
      Animated.timing(item.progress, {
        toValue: 1,
        duration: 5000,
        easing: Easing.linear,
      }).start(); <!-- Here is my attempt in trying to animate that one specific animation. 


      render(){
        return(
          <FlatList
            data={this.state.dataSource}
            renderItem={({item}) => 
            <View>
              <View>
                <Text>Work in progress</Text>
                <View>
                  <TouchableHighlight
                    onPress={() => this.animate(item)}>
                    <Animation
                      progress={item.progress}
                      source={require('../tools/animations/heart_icon.json')}
                    />
                  </TouchableHighlight>

                  <Text> Hello</Text>

                </View>

              </View>


            </View>
          }
          keyExtractor={(item, index) => index.toString()}
        />
      );
    }
  }

Question:

I am using linear gradient from lottie, the code is working fine with ios but in android its not fitting to full screen, there is some kind of padding between gradient and the view. On ios its working fine...i tried many ways, buy including padding to 0 and even margin to -20, but nothing worked...any solution?

import React, { Component } from 'react';
import {
  Text,
  StyleSheet,
  View,
  Platform,
  Dimensions
} from 'react-native';
import SignInScreen from './src/screens/auth/SignInScreen';
import Animation from 'lottie-react-native';

 import anim from './assets/gradient_animated_background.json';
const height = Dimensions.get('window').height + 20;
const width = Dimensions.get('window').width + 20;

export default class App extends Component {
  componentDidMount() {
    this.animation.play();
  }

  render() {
    return (
      <View style={styles.container}>
          <Animation
            ref={animation => {
              this.animation = animation;
            }}
            style={{
              backgroundColor: 'red',
              height: '100%',
              width: '100%'
            }}
            loop={true}
            source={Platform.OS === 'ios' ? anim : 'gradient_animated_background.json'}
          />
       </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'blue',
    flex: 1,
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
    color: '#ffffff'
  }
});

The animation, and when ever i keep changing the width and height..it forcefully tries to maintain the aspect radio...(not sure though)

{"v":"4.6.10","fr":15,"ip":0,"op":155,"w":1080,"h":1920,"nm":"background","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[540,960,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1160,880]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect"},{"ty":"st","c":{"a":0,"k":[0.9960784,0.7843137,0.145098,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"gf","o":{"a":0,"k":100},"r":1,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[0,0.511,0.89,0.283,0.5,0.334,0.873,0.583,1,0.156,0.857,0.882],"e":[0,0.726,0.283,0.89,0.5,0.441,0.356,0.886,1,0.156,0.429,0.882]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":31,"s":[0,0.726,0.283,0.89,0.5,0.441,0.356,0.886,1,0.156,0.429,0.882],"e":[0,0.89,0.283,0.283,0.5,0.886,0.553,0.219,1,0.882,0.823,0.156]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":61,"s":[0,0.89,0.283,0.283,0.5,0.886,0.553,0.219,1,0.882,0.823,0.156],"e":[0,0,0.312,0.737,0.5,0.078,0.597,0.754,1,0.156,0.882,0.771]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":91,"s":[0,0,0.312,0.737,0.5,0.078,0.597,0.754,1,0.156,0.882,0.771],"e":[0,0.51,0.89,0.282,0.5,0.333,0.873,0.582,1,0.157,0.855,0.882]},{"t":120}]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[-430.769,-404.573],"e":[23.726,-364.48],"to":[75.7491683959961,6.68213844299316],"ti":[-123.915840148926,-8.51547145843506]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":31,"s":[23.726,-364.48],"e":[312.726,-353.48],"to":[123.915840148926,8.51547145843506],"ti":[-1.00208830833435,-1.83333337306976]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":61,"s":[312.726,-353.48],"e":[29.739,-353.48],"to":[1.00208830833435,1.83333337306976],"ti":[120.055290222168,0.60746711492538]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":91,"s":[29.739,-353.48],"e":[-407.606,-357.125],"to":[-120.055290222168,-0.60746711492538],"ti":[72.8907089233398,0.60746711492538]},{"t":120}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[374.412,342.611],"e":[22.822,357.191],"to":[-58.5984153747559,2.42986845970154],"ti":[132.520950317383,-7.89707231521606]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":31,"s":[22.822,357.191],"e":[-420.714,389.994],"to":[-132.520950317383,7.89707231521606],"ti":[-4.68509674072266,-7.89707231521606]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":61,"s":[-420.714,389.994],"e":[50.932,404.573],"to":[4.68509674072266,7.89707231521606],"ti":[-132.918350219727,4.25226974487305]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":91,"s":[50.932,404.573],"e":[376.797,364.48],"to":[132.918350219727,-4.25226974487305],"ti":[-54.3107261657715,6.68213844299316]},{"t":120}]},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[93.29,219.491],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":155,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":1,"nm":"Deep Red Solid 1","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[540,960,0]},"a":{"a":0,"k":[540,960,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"sw":1080,"sh":1920,"sc":"#be2a2a","ip":0,"op":155,"st":0,"bm":0,"sr":1}]}

Answer:

What you are missing is the resizeMode, which is not specified on the documentation page, but if you look at the source you'll find it.

Here's something that works if you don't care to lose a few pixels:

import React, { Component } from 'react';

import {
  StyleSheet,
  View,
  Dimensions
} from 'react-native';
import LottieView from 'lottie-react-native';
import { anim } from './assets/animation.json';

const { height, width } = Dimensions.get('window')

export default class App extends Component {
  componentDidMount() {
    this.animation.play();
  }

  render() {
    return (
      <View style={styles.container}>
          <LottieView
            ref={animation => {
              this.animation = animation;
            }}
            style={{
              width: width + 10,
              height: height,
              marginLeft: - 5
            }}
            resizeMode='cover'
            loop={true}
            source={anim}
          />
       </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'blue',
    flex: 1,
  },
});

Notice that I've exported the asset a bit differently since I tried this on https://snack.expo.io and for some reason they don't like JSON files 😁, so now the assets look something

/assets/animation.json.js

export const anim = {"v":"4.6.10","fr":15,"ip":0,"op":155,"w":1080,"h":1920,"nm":"background","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[540,960,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1160,880]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect"},{"ty":"st","c":{"a":0,"k":[0.9960784,0.7843137,0.145098,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"gf","o":{"a":0,"k":100},"r":1,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[0,0.511,0.89,0.283,0.5,0.334,0.873,0.583,1,0.156,0.857,0.882],"e":[0,0.726,0.283,0.89,0.5,0.441,0.356,0.886,1,0.156,0.429,0.882]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":31,"s":[0,0.726,0.283,0.89,0.5,0.441,0.356,0.886,1,0.156,0.429,0.882],"e":[0,0.89,0.283,0.283,0.5,0.886,0.553,0.219,1,0.882,0.823,0.156]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":61,"s":[0,0.89,0.283,0.283,0.5,0.886,0.553,0.219,1,0.882,0.823,0.156],"e":[0,0,0.312,0.737,0.5,0.078,0.597,0.754,1,0.156,0.882,0.771]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":91,"s":[0,0,0.312,0.737,0.5,0.078,0.597,0.754,1,0.156,0.882,0.771],"e":[0,0.51,0.89,0.282,0.5,0.333,0.873,0.582,1,0.157,0.855,0.882]},{"t":120}]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[-430.769,-404.573],"e":[23.726,-364.48],"to":[75.7491683959961,6.68213844299316],"ti":[-123.915840148926,-8.51547145843506]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":31,"s":[23.726,-364.48],"e":[312.726,-353.48],"to":[123.915840148926,8.51547145843506],"ti":[-1.00208830833435,-1.83333337306976]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":61,"s":[312.726,-353.48],"e":[29.739,-353.48],"to":[1.00208830833435,1.83333337306976],"ti":[120.055290222168,0.60746711492538]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":91,"s":[29.739,-353.48],"e":[-407.606,-357.125],"to":[-120.055290222168,-0.60746711492538],"ti":[72.8907089233398,0.60746711492538]},{"t":120}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[374.412,342.611],"e":[22.822,357.191],"to":[-58.5984153747559,2.42986845970154],"ti":[132.520950317383,-7.89707231521606]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":31,"s":[22.822,357.191],"e":[-420.714,389.994],"to":[-132.520950317383,7.89707231521606],"ti":[-4.68509674072266,-7.89707231521606]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":61,"s":[-420.714,389.994],"e":[50.932,404.573],"to":[4.68509674072266,7.89707231521606],"ti":[-132.918350219727,4.25226974487305]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":91,"s":[50.932,404.573],"e":[376.797,364.48],"to":[132.918350219727,-4.25226974487305],"ti":[-54.3107261657715,6.68213844299316]},{"t":120}]},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[93.29,219.491],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":155,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":1,"nm":"Deep Red Solid 1","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[540,960,0]},"a":{"a":0,"k":[540,960,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"sw":1080,"sh":1920,"sc":"#be2a2a","ip":0,"op":155,"st":0,"bm":0,"sr":1}]}

Question:

I'm trying to create a save button (basically to bookmark a screen) that toggles animation whether a user taps the button to "save" an item or remove the "save". Tapping the button should call one of the animations whether it's the "on" animation or the "off" animation.

Unfortunately besides iOS I can't find any documentation on toggling an animation on a button in React Native. Any ideas would be appreciated.

Here's the component:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
    StyleSheet,
    TouchableOpacity
} from 'react-native';
import _ from 'lodash';

import { connect } from 'react-redux';
import { toggleSaved } from '../../actions';
import Animation from 'lottie-react-native';

import onAnimation from '../animations/heart_On.json';
import offAnimation from '../animations/heart_Off.json';

class SaveButton extends Component {
    isSaved = () => {
        let item = this.props.item;
        if (_.find(this.props.saved, function(i) { return i.type == item.type && i.id == item.id; })) {
            return true;
        } else {
            return false;
        }
    }

    toggleSaved = (saved, item) => {
        const { dispatch } = this.props;
        dispatch(toggleSaved(saved, item));
        this.animation.play();
    }

    render() {
        return (
            <TouchableOpacity
                onPress={ () => this.toggleSaved(this.props.saved, this.props.item) }
            >
                <Animation
                    ref={ animation => {
                        this.animation = animation;
                    } }
                    style={ styles.icon }
                    loop={ false }
                    source={ this.isSaved() ? onAnimation: offAnimation }
                />
            </TouchableOpacity>
        );
    }
}

SaveButton.propTypes = {
    dispatch: PropTypes.func.isRequired,
    saved: PropTypes.array.isRequired,
    item: PropTypes.object.isRequired
};

const styles = StyleSheet.create({
    icon: {
        width: 30,
        height: 30,
        marginTop: 5,
        marginRight: 10
    }
});

function mapStateToProps(state) {
    const { saved } = state.saved;

    return {
        saved
    };
}

export default connect(mapStateToProps)(SaveButton);

Answer:

I'd suggest you to have two separate animation elements and then, depending on the state, show or hide (and of course .play()) them accordingly.

So (code in half-pseudo to get the idea):

  • state prop like isSaved, which defines which animation is currently on stage,
  • when saving (or removing) toggle the state to show correct animation and call the this.refToCorrectAnimation.play().

constructor(props) {
  super(props);
  this.state = {
    isSaved: false,
  };
}

isSavedVisible() {
  return (this.state.isSaved) ? { display: 'flex' } : { display: 'none' };
}

isRemovedVisible() {
  return (!this.state.isSaved) ? { display: 'flex' } : { display: 'none' };
}


toggleSaved(...) {
  if (this.state.isSaved) {
    this.isSavedAnimation.play();
  } else {
    this.isRemovedAnimation.play();
}

render() {
  return (
    <TouchableOpacity onPress={() => this.toggleSaved()} ...>
      <Animation style={this.isSavedVisible()} ref={(animation) = this.isSavedAnimation = animation} ... />
      <Animation style={this.isRemovedVisible()} ref={(animation) = this.isRemovedAnimation = animation} ... />
    </TouchableOpacity>
  )
}