Home > Blockchain >  FutureBuilder Issues with loading new data
FutureBuilder Issues with loading new data

Time:01-19

I am attempting to implement a play button, that when clicked will play a selected audio file from a ListTile.

Here is the code for getting the filepath from the list tile:

Future getPath() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    var selectedCard = prefs.getInt('selectedIndex');
    var selectedPath = prefs.getString('selectedPath') ?? "";
    
    return selectedPath;
  }

Here is where the play button is built:

Widget build(BuildContext context) {
    final AudioPlayerController audioController =
        Provider.of<AudioPlayerController>(context, listen: true);

    return FutureBuilder(
        future: getPath(),
        builder: ((context, snapshot) {
          if (snapshot.hasData) {
            String path = snapshot.data.toString();
            var apc = AudioPlayerController(path.toString());
            return StreamProvider(
                initialData: apc.playingState ?? PlayState.ready,
                create: (_) => apc.playingStateStream,
                builder: (context, child) {
                  return Container(
                      child: FloatingActionButton(
                          heroTag: "Play",
                          backgroundColor: AppColor.AppLightBlueColor,
                          foregroundColor: AppColor.AppDarkBlueColor,
                          focusColor: AppColor.AppLightBlueColor,
                          splashColor: AppColor.AppLightBlueColor,
                          elevation: 0,
                          onPressed: () async {

                            if (apc.playingState == PlayState.playing) {
                              await apc.pause();
                            } else if (apc.playingState == PlayState.ready) {
                              await apc.setSource(path);
                              await apc.play();
                            } else if (apc.playingState == PlayState.paused) {
                              await apc.resume();
                            }
                          },
                          child: StreamBuilder(
                              stream: apc.playingStateStream,
                              builder: (context, snapshot) {
                                switch (snapshot.data) {
                                  case PlayState.playing:
                                    return const Icon(
                                      Icons.pause,
                                      size: 50,
                                    );
                                  case PlayState.paused:
                                    return const Icon(
                                      Icons.play_arrow_rounded,
                                      size: 50,
                                    );
                                  default:
                                    debugPrint("Default");
                                    // stderr.writeln(snapshot);
                                    return const Icon(
                                      Icons.play_arrow_rounded,
                                      size: 50,
                                    );
                                }
                              })));

Here is where the path comes from in the ListTile:

              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(15.0),
              ),
              elevation: 0,
              child: Column(
                children: [
                  Container(
                      decoration: BoxDecoration(
                          borderRadius: borderRadius,
                          color: selectedIndex == index
                              ? AppColor.HighlightTileColor
                              : AppColor.AppLightBlueColor),
                      alignment: Alignment.center,
                      height: MediaQuery.of(context).size.height / 9,
                      child: ListTile(
                        selected: selectedIndex == index,
                        shape:
                            RoundedRectangleBorder(borderRadius: borderRadius),
                        onTap: () async {
                          SharedPreferences prefs =
                              await SharedPreferences.getInstance();
                          prefs.setInt('selectedIndex', index);
                          prefs.setString('selectedPath', file1);
                          setState(() {
                            selectedIndex = index;
                            // selectedPath = file1;
                          });

The issue I am facing, is that the play button works to play and pause audio properly, but the futurebuilder is not updating when selecting a new listtile.

Tried to implement a setState inside of the getPath() function, but it created an infinite loop

CodePudding user response:

Once a Future is completed, even if you use setState, it will not re-run but use the previous result during the current build. To make it refresh, you need to define the future as a variable in a member of a stateful widget's state class, and assign a new value to it with setState.

So convert it into a stateful class if it is not stateful yet, and add a member variable:

late Future<String> _getPathFuture;

In an initState function, assign an initial value to it:

@override
void initState() {
  super.initState();
  _getPathFuture =  getPath();
}

You also have to modify getPath to indicate its return type:

Future<String> getPath() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  var selectedCard = prefs.getInt('selectedIndex');
  var selectedPath = prefs.getString('selectedPath') ?? "";
    
  return Future.value(selectedPath);
}

Now use this variable in the FutureBuilder, set type here as well to String:

return FutureBuilder<String>(
  future: _getPathFuture,
  ...
);

After this, once you are sure that the new values are saved to shared preferences, call setState like:

setState(() {
  _getPathFuture = getPath();
});

Two comments:

  1. Writing to the shared preferences is async. To make sure the write operation is complete, use await prefs.setInt....

  2. Since reading shared preferences is sync, the entire FutureBuilder is only needed because SharedPreferences.getInstance() is async. But there is no real reason to read its value every time you access is. You can save this value somewhere in you app when is starts up, even in main function, store it so that you can access it from you app.

  • Related