I want to show skeleton widget while the data still loading so I used if-else in FutureBuilder widget.

Here the skeleton code

class Skeleton extends StatefulWidget {
  final double height;
  final double width;

  Skeleton({Key key, this.height = 20, this.width = 200 }) : super(key: key);

  createState() => SkeletonState();
}

class SkeletonState extends State<Skeleton> with SingleTickerProviderStateMixin {
  AnimationController _controller;

  Animation gradientPosition;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: Duration(milliseconds: 1500), vsync: this);

    gradientPosition = Tween<double>(
      begin: -3,
      end: 10,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.linear
      ),
    )..addListener(() {
      setState(() {});
    });

    _controller.repeat();
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        width:  widget.width,
        height: widget.height, 
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment(gradientPosition.value, 0),
            end: Alignment(-1, 0),
            colors: [Colors.black12, Colors.black26, Colors.black12]
          )
        ),
    );
  }
}

and here I tried to use the skeleton widget

FutureBuilder(
              future: CategoryService.list(),
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                print(snapshot.error);
                return snapshot.hasData
                    ? SingleChildScrollView(
                        scrollDirection: Axis.horizontal,
                        child: CategoryList(
                          categories: snapshot.data,
                        ),
                      )
                    : snapshot.hasError
                        ? Text(
                            snapshot.error.toString(),
                          )
                        : Skeleton(
                            height: 200,
                            width: 200,
                          );
              },
            ),

Then I got this Error

Exception caught by animation library

'package:flutter/src/widgets/framework.dart': Failed assertion: line 4182 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is not true. ════════════════════════════════════════════════════════════════════════════════ Another exception was thrown: 'package:flutter/src/widgets/framework.dart': Failed assertion: line 4182 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is not true.

I got this Error but the App still running without problem but this Error shows in Debug console and always repeat the Error all time while the screen is running.

I did reload and I stop it and re run it but that was useless, I think the problem in the dispose of Skeleton widget.

1

There are 1 answers

0
chunhunghan On BEST ANSWER

You can copy paste run full code below
You can move _controller.dispose(); before super.dispose();
code snippet

@override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

working demo

enter image description here

full code

import 'package:flutter/material.dart';

class Skeleton extends StatefulWidget {
  final double height;
  final double width;

  Skeleton({Key key, this.height = 20, this.width = 200}) : super(key: key);

  createState() => SkeletonState();
}

class SkeletonState extends State<Skeleton>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  Animation gradientPosition;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: Duration(milliseconds: 1500), vsync: this);

    gradientPosition = Tween<double>(
      begin: -3,
      end: 10,
    ).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    )..addListener(() {
        setState(() {});
      });

    _controller.repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: widget.width,
      height: widget.height,
      decoration: BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment(gradientPosition.value, 0),
              end: Alignment(-1, 0),
              colors: [Colors.black12, Colors.black26, Colors.black12])),
    );
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class CategoryService {
  static Future<String> list() async {
    await Future.delayed(Duration(seconds: 5), () {});
    return Future.value("123");
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: FutureBuilder(
        future: CategoryService.list(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          print(snapshot.error);
          return snapshot.hasData
              ? SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Text(
                    snapshot.data,
                  ),
                )
              : snapshot.hasError
                  ? Text(
                      snapshot.error.toString(),
                    )
                  : Skeleton(
                      height: 200,
                      width: 200,
                    );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}