juergen villasmil
juergen villasmil

Reputation: 11

How to use componentWillUnmount()?

I am new using React-Native and I'm creating a music player for a streaming app and almost everything works fine, it plays in the background but when I want to switch to another album or playlist it does not cut the song that is playing to play the new one, it plays the previous song and the new one at the same time.

It shows me this warning:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

But I don't know how to cancel all subscriptions and asynchronous tasks.

Here is my code.

import {
  StyleSheet,
  TouchableOpacity,
  View,
  Image,
  ImageBackground,
  Slider,
} from "react-native";
import { Title, Text } from "react-native-paper";
import { LinearGradient } from "expo-linear-gradient";
import { Button } from "../components/Button";
import { Audio, Video } from "expo-av";
import firebase from "../utils/firebase";
import "firebase/firestore";
import { Ionicons } from "@expo/vector-icons";

export default function ReproductorAudio(props) {
  const { route } = props;
  const { canciones } = route.params;
  const [duration, setDuration] = useState(0);
  const [totalDuration, setTotalDuration] = useState(0);

  const cancionesPlaylist = canciones;

  console.log(cancionesPlaylist);

  return (
    <ReproductorMusica
      duration={duration}
      cancionesPlaylist={cancionesPlaylist}
      totalDuration={totalDuration}
    />
  );
}

class ReproductorMusica extends React.Component {
  state = {
    isPlaying: false,
    playbackInstance: null,
    currentIndex: 0,
    duration: 0,
    volume: 1.0,
    isBuffering: false,
    isMounted: false,
    totalDuration: 0,
  };

  async componentDidMount() {
    this.isMounted = true;

    try {
      await Audio.setAudioModeAsync({
        allowsRecordingIOS: false,
        interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
        playsInSilentModeIOS: true,
        interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
        shouldDuckAndroid: true,
        staysActiveInBackground: true,
        playThroughEarpieceAndroid: true,
      });

      this.loadAudio();
    } catch (e) {
      console.log(e);
    }
  }

  async componentWillUnmount() {
    Audio.setAudioModeAsync({
      allowsRecordingIOS: false,
      interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
      playsInSilentModeIOS: true,
      interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
      shouldDuckAndroid: true,
      staysActiveInBackground: true,
      playThroughEarpieceAndroid: true,
    });
  }

  async loadAudio() {
    const { currentIndex, isPlaying, volume } = this.state;

    try {
      const playbackInstance = new Audio.Sound();
      const source = {
        uri: this.props.cancionesPlaylist[currentIndex].song,
      };

      const status = {
        shouldPlay: isPlaying,
        volume,
      };

      playbackInstance.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
      await playbackInstance.loadAsync(source, status, false);
      this.setState({ playbackInstance });
    } catch (e) {
      console.log(e);
    }
  }

  onPlaybackStatusUpdate = (status) => {
    this.setState({
      isBuffering: status.isBuffering,
    });
  };

  handlePlayPause = async () => {
    const { isPlaying, playbackInstance } = this.state;
    isPlaying
      ? await playbackInstance.pauseAsync()
      : await playbackInstance.playAsync();

    this.setState({
      isPlaying: !isPlaying,
    });
  };

  handlePreviousTrack = async () => {
    let { playbackInstance, currentIndex } = this.state;
    if (playbackInstance) {
      await playbackInstance.unloadAsync();
      currentIndex < this.props.cancionesPlaylist.length - 1
        ? (currentIndex -= 1)
        : (currentIndex = 0);
      this.setState({
        currentIndex,
      });
      this.loadAudio();
    }
  };

  handleNextTrack = async () => {
    let { playbackInstance, currentIndex } = this.state;
    if (playbackInstance) {
      await playbackInstance.unloadAsync();
      currentIndex < this.props.cancionesPlaylist.length - 1
        ? (currentIndex += 1)
        : (currentIndex = 0);
      this.setState({
        currentIndex,
      });
      this.loadAudio();
    }
  };

  renderFileInfo() {
    const {
      playbackInstance,
      currentIndex,
      duration,
      totalDuration,
    } = this.state;

    return playbackInstance ? (
      <View style={styles.trackInfo}>
        <Image
          style={styles.albumCover}
          source={{
            uri: this.props.cancionesPlaylist[currentIndex].image,
          }}
        />
        <Title style={[styles.trackInfoText, styles.largeText]}>
          {this.props.cancionesPlaylist[currentIndex].name}
        </Title>
        <Title style={[styles.trackInfoText, styles.smallText]}></Title>
        <View style={styles.progressContainer}>
          <Slider
            totalDuration={this.props.cancionesPlaylist[currentIndex].duracion}
            onValueChange={(value) =>
              this.props.cancionesPlaylist[duration](value)
            }
          />
          <View style={styles.durationContainer}>
            <Text style={styles.durationTextLeft}>{duration}</Text>
            <Text style={styles.durationTextRight}>
              -{(totalDuration - duration).toFixed(2)}
            </Text>
          </View>
        </View>
        {/*<Title style={[styles.trackInfoText, styles.smallText]}>
          {this.props.cancionesPlaylist[currentIndex].pista}
        </Title>*/}
      </View>
    ) : null;
  }

  render() {
    const { playbackInstance, currentIndex } = this.state;

    return (
      <ImageBackground
        style={styles.backgroundImage}
        source={{
          uri: this.props.cancionesPlaylist[currentIndex].image,
        }}
        blurRadius={25}
      >
        <View style={styles.container}>
          {this.renderFileInfo()}
          <View style={styles.controls}>
            <TouchableOpacity
              style={styles.control}
              onPress={this.handlePreviousTrack}
            >
              <Ionicons
                name="arrow-back-circle-outline"
                size={48}
                color="#fff"
              />
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.control}
              onPress={this.handlePlayPause}
            >
              {this.state.isPlaying ? (
                <Ionicons name="ios-pause" size={48} color="#fff" />
              ) : (
                <Ionicons name="ios-play-circle" size={48} color="#fff" />
              )}
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.control}
              onPress={this.handleNextTrack}
            >
              <Ionicons
                name="arrow-forward-circle-outline"
                size={48}
                color="#fff"
              />
            </TouchableOpacity>
          </View>
        </View>
      </ImageBackground>
    );
  }
}

const styles = StyleSheet.create({
  backgroundImage: {
    position: "absolute",
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },
  container: {
    flex: 1,
    backgroundColor: "rgba(189,0,0,0.3)",
    alignItems: "center",
    justifyContent: "center",
  },
  albumCover: {
    width: 250,
    height: 250,
    borderRadius: 10,
    borderWidth: 5,
    borderColor: "#fff",
  },
  trackInfo: {
    padding: 40,
    paddingBottom: 0,
    //backgroundColor: "#000",
  },
  trackInfoText: {
    textAlign: "center",
    flexWrap: "wrap",
    color: "#fff",
  },
  largeText: {
    fontSize: 22,
  },
  smallText: {
    fontSize: 16,
  },
  control: {
    margin: 20,
    color: "#fff",
  },
  controls: {
    flexDirection: "row",
    color: "#fff",
  },
  durationContainer: {
    flexDirection: "row",
  },
  durationTextLeft: {
    flex: 0.5,
    textAlign: "left",
    fontSize: 16,
    fontWeight: "bold",
    color: "white",
  },
  durationTextRight: {
    flex: 0.5,
    textAlign: "right",
    fontSize: 16,
    fontWeight: "bold",
    color: "white",
  },
});  ```

Upvotes: 1

Views: 456

Answers (2)

WitaloBenicio
WitaloBenicio

Reputation: 3682

This seems to be an architectural problem.

If you are in the ReproductorMusica and need to leave that screen to go to the AlbumScreen you're not unloading the last instance (which you've lost, since you only had a reference in the ReproductorMusica).

To solve this problem, your Audio.Sound needs to be "global and unique". So you can access from any screen, and it's always the same instance.

Upvotes: 1

Caio Grossi
Caio Grossi

Reputation: 39

I really encourage you to change your components to function components.You are using both for no reason, and class components are harder to use state management. You can change to function components and use just "useEffect" hook to do everything you need. Read more about here: https://reactjs.org/docs/hooks-effect.html

Upvotes: 0

Related Questions