Controlling Carousel Slider from Stream (Flutter)

Issue

I have data coming from a stream mainly a queue and current mediaItem of the queue. So the mediaItem is always changing.
I got the index using

index = queue.indexOf(mediaItem);

now I want to set the carousel’s current page to my index, I added the index on the initialPage of the Carousel but it works only once when the index changes it doesn’t change the current page.

My Stream looks like this

//this stream is initialized inside initState()
stream = Rx.combineLatest3<List<MediaItem>, MediaItem, PlaybackState, ScreenState>(
  AudioService.queueStream,
  AudioService.currentMediaItemStream,
  AudioService.playbackStateStream,
      (queue, mediaItem, playbackState) => ScreenState(queue, mediaItem, playbackState)
);


Scaffold(
  body: new Center(
    child: StreamBuilder<ScreenState>(
      stream: stream,
      builder: (context, snapshot) {
        final screenState = snapshot.data;
        final queue = screenState?.queue;
        final mediaItem = screenState?.mediaItem;
        final state = screenState?.playbackState;
        final basicState = state?.basicState ?? BasicPlaybackState.none;
        int index = queue?.indexWhere((MediaItem mediaItemX){return (mediaItem?.id == mediaItemX.id);});
        return (queue!=null&& mediaItem!=null && basicState != null && index != null) 
          ? mainView(queue, mediaItem, basicState, state, index)
          : Container(
            child: Center(
              child: SpinKitWave(
                color: Colors.white70,
              ),
            ),
          );
      },
    ),
  ),
),

Main view

Widget mainView(List<MediaItem> queue, MediaItem mediaItemX, BasicPlaybackState basicState, PlaybackState state, int index){
  return CarouselSlider.builder(
    itemCount: queue.length,
    initialPage: queue.indexOf(mediaItemX),//only works first time
    itemBuilder: (BuildContext context, int itemIndex) {
      Stack(
        children: <Widget>[
          ClipRRect(
            borderRadius: BorderRadius.circular(8.0),
            child: Container(
              padding: EdgeInsets.fromLTRB(0,0,0,0),
              child: CachedNetworkImage(
                height: ScreenUtil().setWidth(1200),
                width: ScreenUtil().setWidth(1200),
                imageUrl: queue[itemIndex].artUri,
                fit: BoxFit.cover,
                placeholder: (context, url) => new SpinKitWave(color: Colors.white30, size: 30.0,),
                errorWidget: (context, url, error) => new Image.asset(
                  'images/addplaylist.png',
                  color: Colors.white30,
                ),
              ),
            )
          )
        ],
      ),
    }
  );
}

The issue is when mediaItem changes from another place, the carousel is needed to change its index based on the stream change of data, In other words, a functionality almost same as currentPage inside Carousel so that every time index changes then the currentPage is set to the new data from the stream or to control the Carousel from the stream.
A solution or a hint with an example is highly appreciated.

Additional Information: I am using carousel_slider: ^1.4.1

Solution

initialPage works properly with empty containers (see code below), seems to me that is missing some key maybe in cached image network or your container, try put a UniqueKey() to ensure that widget tree will detect the change when new snapshot comes…

Example working with StreamBuilder and slider:

import 'dart:async';
import 'dart:math';

import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(body: MyHomePage()),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final StreamController sc = StreamController();
  final rng = Random();

  final containers = [
    Container(color: Colors.pink),
    Container(color: Colors.black),
    Container(color: Colors.yellow),
    Container(color: Colors.brown),
  ];
  @override
  void initState() {
    super.initState();

    Timer.periodic(
      Duration(seconds: 5),
      (t) {
        sc.add(rng.nextInt(containers.length));
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: sc.stream,
      builder: (context, snapshot) {
        if (snapshot.hasData == false) {
          return CircularProgressIndicator();
        }
        return Container(
          child: mainView(containers, containers[snapshot.data]),
        );
      },
    );
  }

  Widget mainView(
    List<Container> queue,
    Container mediaItemX,
  ) {
    return CarouselSlider.builder(
      itemCount: queue.length,
      initialPage: queue.indexOf(mediaItemX), //only works first time
      itemBuilder: (BuildContext context, int itemIndex) {
        print('changed');
        
        return mediaItemX;
      },
    );
  }
}

Answered By – Renê Guilherme Nucci

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *