flutterdartflutter-pageview

Flutter - How to keep the page alive when changing it with PageView or BottomNavigationBar


i'm making audio app with PageView and BottomNavigationBar, it should run the audio when isSelected is true and it's working but when I change pages it stop working and isSelected become false again, how to prevent that from happening? i'm also using AudioPlayers pagckage.

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int selectedIndex = 0;
  final PageController pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: PageView(
          controller: pageController,
          children: <Widget>[
            TimerPage(),
            TodoPage(),
            CalenderPage(),
            MusicPage(),
            SettingsPage(),
          ],
          onPageChanged: (pageIndex) {
            setState(() {
              selectedIndex = pageIndex;
            });
          },
        ),
      ),
      bottomNavigationBar: SizedBox(
        height: 70,
        child: ClipRRect(
          borderRadius: const BorderRadius.only(
            topRight: Radius.circular(25),
            topLeft: Radius.circular(25),
          ),
          child: BottomNavigationBar(
            onTap: (selectedIndex) {
              setState(() {
                pageController
                  ..animateToPage(selectedIndex,
                      duration: Duration(milliseconds: 500),
                      curve: Curves.ease);
              });
            },
            backgroundColor: MyColors.lightgray,
            selectedItemColor: MyColors.accentRed,
            unselectedItemColor: MyColors.disabledGrey,
            selectedFontSize: 15,
            unselectedFontSize: 15,
            type: BottomNavigationBarType.fixed,
            currentIndex: selectedIndex,
            showSelectedLabels: false,
            showUnselectedLabels: false,
            items: [
              BottomNavigationBarItem(
                icon: const Icon(FontAwesomeIcons.clock),
                label: "",
              ),
              BottomNavigationBarItem(
                icon: const FaIcon(FontAwesomeIcons.check),
                label: "",
              ),
              BottomNavigationBarItem(
                icon: const FaIcon(FontAwesomeIcons.calendarAlt),
                label: "",
              ),
              BottomNavigationBarItem(
                icon: const FaIcon(FontAwesomeIcons.music),
                label: "",
              ),
              BottomNavigationBarItem(
                icon: const FaIcon(FontAwesomeIcons.ellipsisH)
                label: "",
              ),
            ],
          ),
        ),
      ),
    );
  }
}

the play button:

class SoundChip extends StatefulWidget {
  final String title;
  final String image;
  final String audioName;
  final VoidCallback onPress;
  SoundChip({Key key, this.title, this.image, this.onPress, this.audioName})
      : super(key: key);

  @override
  _SoundChipState createState() => _SoundChipState();
}

class _SoundChipState extends State<SoundChip> {
  bool isSelected = false;

  AudioPlayer audioPlayer = AudioPlayer();
  PlayerState audioPlayerState = PlayerState.PAUSED;
  AudioCache audioCache;

  play() async {
    await audioCache.loop(widget.audioName,
        stayAwake: true, mode: PlayerMode.LOW_LATENCY);
  }

  pause() async {
    await audioPlayer.pause();
  }

  @override
  void initState() {
    super.initState();
    audioCache = AudioCache(fixedPlayer: audioPlayer);
    audioPlayer.onPlayerStateChanged.listen((PlayerState state) {
      setState(() {
        audioPlayerState = state;
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
    audioPlayer.release();
    audioPlayer.dispose();
    audioCache.clearAll();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        if (audioPlayerState == PlayerState.PLAYING) {
          pause();
          isSelected = false;
        } else {
          play();
          isSelected = true;
        }
        widget.onPress();
      },
      child: AnimatedOpacity(
        opacity: isSelected ? 1 : 0.5,
        duration: Duration(milliseconds: 100),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(20),
          child: AnimatedContainer(
            duration: Duration(seconds: 1),
            width: 160,
            height: 100,
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage(widget.image),
                fit: BoxFit.cover,
              ),
            ),
            child: Center(
                child: Text(
              widget.title,
              style: TextStyle(
                fontSize: 30,
                shadows: [
                  Shadow(
                    color: Colors.black,
                    blurRadius: 20,
                  ),
                ],
              ),
            )),
          ),
        ),
      ),
    );
  }
}

Solution

  • Add AutomaticKeepAliveClientMixin to your page that you want to keep alive even if it is not in focus in the PageView.

    How to add AutomaticKeepAliveClientMixin?

    1. Add with AutomaticKeepAliveClientMixin to your widget's state class.
    class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
    ...
    }
    
    1. Add wantKeepAlive getter to your widget's state.
    class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
    @override 
    bool get wantKeepAlive => true;
    ...
    }
    
    1. Add super.build(context) to the build method of your widget's state.
    class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
     bool get wantKeepAlive => true;
    
    @override
    Widget build(BuildContext context) {
      super.build(context);
      return ...
     }
    }