0

I am somewhat confused as to why a widget is not properly redrawn, even though the build method is invoked.

Basically a card is drawn (SplashCard), filling most of the screen. This card is swipable (tinder style). When one slides it to the right, it should be replaced by a new card. Therefore I just update the index of the list and since the build method is executed, I expected a new card (with data from the new index) is created and shown. However, when using setState and updating the index (to the new card), even though all build methods are called properly and the new card has the correct data, it is not drawn. The screen stays empty. Any ideas as to why this happens? Where am I thinking wrong?

Screenshots: before swipe, after swipe

See the main code here:

class SwipeLearning extends StatefulWidget {
  const SwipeLearning({Key? key, required this.title, required this.cardList})
      : super(key: key);

  final String title;
  final List<FlashCard> cardList;

  @override
  State<SwipeLearning> createState() => _SwipeLearningState();
}

class _SwipeLearningState extends State<SwipeLearning> {
  late double _learningProgress;
  late int _currentCardIndex;
  late Widget _currentCard;

  @override
  initState() {
    super.initState();
    _learningProgress = 0.0;
    _currentCardIndex = 0;
  }

  void _nextCard() {
    setState(() {
      _learningProgress += 1.0 / widget.cardList.length;
      _currentCardIndex++;
    });
  }

  Widget _createStandardSplashCard(FlashCard card) {
    return SplashCard(
      onSwipeRight: (details) => _nextCard(),
      front: SplashCardContent(content: Text(card.front)),
      back: SplashCardContent(content: Text(card.back)),
    );
  }

  @override
  Widget build(BuildContext context) {
    print('SwipeLearning built method invoked');

    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
        titleTextStyle: Theme.of(context).textTheme.headline6,
        backgroundColor: Colors.transparent,
        elevation: 0,
        iconTheme: IconThemeData(
          color: Colors.black,
        ),
      ),
      body: Column(
        children: [
          LinearProgressIndicator(
            backgroundColor: Colors.grey,
            value: _learningProgress,
          ),
          Expanded(
            child: _createStandardSplashCard(widget.cardList[_currentCardIndex]),
          ),
        ],
      ),
    );
  }
}

and the definitions of each card is given here, FlipCard is from flip_card: ^0.5.0, Swipable from flutter_swipable: ^1.0.0.

class SplashCard extends StatelessWidget {
  const SplashCard(
      {Key? key,
      required this.front,
      required this.back,
      this.onSwipeLeft,
      this.onSwipeRight,
      this.onSwipeUp})
      : super(key: key);

  final SplashCardContent front;
  final SplashCardContent back;
  final void Function(Offset finalPosition)? onSwipeLeft;
  final void Function(Offset finalPosition)? onSwipeRight;
  final void Function(Offset finalPosition)? onSwipeUp;

  @override
  Widget build(BuildContext context) {
    print('SplashCard built method invoked');

    return Swipable(
      onSwipeLeft: onSwipeLeft,
      onSwipeRight: onSwipeRight,
      onSwipeUp: onSwipeUp,
      onPositionChanged: (details) {},
      onSwipeCancel: (position, details) {},
      onSwipeDown: (finalPosition) {},
      onSwipeEnd: (position, details) {},
      onSwipeStart: (details) {},
      child: FlipCard(
        direction: FlipDirection.HORIZONTAL,
        front: front,
        back: back,
      ),
    );
  }
}

class SplashCardContent extends StatelessWidget {
  const SplashCardContent({Key? key, required this.content}) : super(key: key);

  final Widget content;

  @override
  Widget build(BuildContext context) {
    print('SplashCardContent built method invoked');

    return Container(
      width: double.infinity,
      height: double.infinity,
      padding: const EdgeInsets.all(5.0),
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(5.0),
        ),
        elevation: 5.0,
        child: content,
      ),
    );
  }
}

class FlashCard {
  final String front;
  final String back;

  const FlashCard({required this.front, required this.back});
}

cardList: [
          FlashCard(front: 'hello', back: 'hallo'),
          FlashCard(front: 'I', back: 'ich'),
          FlashCard(front: 'you', back: 'du'),
          FlashCard(front: 'he', back: 'er'),
          FlashCard(front: 'she', back: 'sie'),
          FlashCard(front: 'it', back: 'es'),
          FlashCard(front: 'we', back: 'wir'),
          FlashCard(front: 'you', back: 'ihr'),
          FlashCard(front: 'they', back: 'sie'),
          FlashCard(front: 'goodbye', back: 'tschüss'),
        ],
Denis
  • 3
  • 2

1 Answers1

0

If you swiped the card out of the screen, then you would only change its content and not the position that would stay out of the screen.

Maybe adding a key: Key(card.front) to the SplashCard a new card would be created at every setState()

Luca Oropallo
  • 474
  • 4
  • 12
  • Wow thanks, that solved it! To clarify, is that an optimization in the dart language? I thought a new SplashCard instance is created when the constructor SplashCard in _createStandardSplashCard(FlashCard card) is called? Or is the position not stored in this widget at all? – Denis Aug 18 '21 at 13:36
  • I'm not sure, but the position of the object is not a characteristic of it, so when it is rebuilded it does not change. – Luca Oropallo Aug 18 '21 at 13:41