I'm building an app that lists multiple running timers on screen which the user can dismiss by swiping the card. To update the timer I am setting state every one second. The problem with this is that the swipe action does not always respond. It animates but does not dismiss.

I have mocked a quick demo to show the problem. If you swipe exactly after the counter updates, it should work... if you swipe before the counter update, it does not.

import 'dart:async';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Timer test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // ignore: unused_field
  late Timer _everySecond;
  int counter = 0;

  @override
  void initState() {
    super.initState();
    // defines a timer
    _everySecond = Timer.periodic(const Duration(seconds: 1), (Timer t) async {
      setState(() {
        counter++;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Timer test'),
      ),
      body: Dismissible(
        key: UniqueKey(),
        onDismissed: (direction) => counter = 0,
        background: Container(color: Colors.black38),
        child: SizedBox(
          height: 120,
          child: Center(
            child: Text(counter.toString()),
          ),
        ),
      ),
    );
  }
}

Is there any other way how to handle this better?


Solution 1: SebastianK

I think your issue is related to not using setState when dismissing the card. Always keep in mind to execute the state changing logic in setState method to notify Flutter for state changes and thus signaling the need to redraw the screen. The following code should do what you want:

import 'dart:async';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Timer test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // ignore: unused_field
  late Timer _everySecond;
  int counter = 0;

  @override
  void initState() {
    super.initState();
    // defines a timer
    _everySecond = Timer.periodic(const Duration(seconds: 1), (Timer t) async {
      setState(() {
        counter++;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Timer test'),
      ),
      body: Dismissible(
        key: UniqueKey(),
        onDismissed: (direction) => setState({counter = 0;}),
        background: Container(color: Colors.black38),
        child: SizedBox(
          height: 120,
          child: Center(
            child: Text(counter.toString()),
          ),
        ),
      ),
    );
  }
}