Refactoring switch case with connection state

Viewed 102

i'm using switch case inside future builder (below) with connection state to view certain widgets in different connection states.

My question is, how can I refactor the switch case so i can use it in other places in the app?

Thanks..

code:

FutureBuilder(
  future: _fetchProductsList,
  builder: (context, AsyncSnapshot<List<ProductsList>> snapshot) {
    switch(snapshot.connectionState){
      case ConnectionState.none:
        return const Text("There is no connection!!");

      case ConnectionState.active:
      case ConnectionState.waiting:
        return const Center(child: CircularProgressIndicator());
      
      case ConnectionState.done:
      if(snapshot.hasData){
        if (snapshot.data!.isNotEmpty){

          List<ProductsList>? product = snapshot.data;

          return ListView.builder(
            primary: false,
            shrinkWrap: true,
            scrollDirection: Axis.horizontal,
            itemCount: product!.length,
            itemBuilder: (BuildContext context,int index){
              return ProductCard(
                img: product[index].image,
                title: product[index].title,
                price: product[index].price,
                rating: product[index].rating.rate,
                discount: 50,
                onTap: () {}
              );
            }
          );
        }else {
          // here your snapshot data is null, has no data...
          return const Center(
            child: Text(
              "No Data Available Right Now!!"
            )
          );
        }
      }
    }
    // By default, show a loading spinner
    return const Center(child: CircularProgressIndicator());
  }
)
2 Answers

You can extract the whole FutureBuilder in a separate widget with a Generic Type to handle any type of data returned by the future.

For example I made a widget called CustomFutureBuilder like the following:

class CustomFutureBuilder<T> extends StatelessWidget {
  final Future<T> future;
  final Function(T data) successBuilder;

  const CustomFutureBuilder({
    Key? key,
    required this.future,
    required this.successBuilder,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<T>(
      future: future,
      builder: (context, AsyncSnapshot<T> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return const Text("There is no connection!!");

          case ConnectionState.active:
          case ConnectionState.waiting:
            return const Center(child: CircularProgressIndicator());

          case ConnectionState.done:
            if (snapshot.hasData) {
              successBuilder(snapshot.data!);
            }
        }
        // By default, show a loading spinner
        return const Center(child: CircularProgressIndicator());
      },
    );
  }
}

Here I made a trick by passing a custom builder method that receives the data after all the checks, you can also pass any number of parameters you want to customize everything.

Usage Example:

CustomFutureBuilder<List>(
  future: _fetchProductsList,
  successBuilder: (data) {
    if (data.isNotEmpty) {
      List<ProductsList>? product = snapshot.data;

      return ListView.builder(
        primary: false,
        shrinkWrap: true,
        scrollDirection: Axis.horizontal,
        itemCount: product!.length,
        itemBuilder: (BuildContext context, int index) {
          return ProductCard(
              img: product[index].image,
              title: product[index].title,
              price: product[index].price,
              rating: product[index].rating.rate,
              discount: 50,
              onTap: () {});
        },
      );
    } else {
      // here your snapshot data is null, has no data...
      return const Center(child: Text("No Data Available Right Now!!"));
    }
  },
);

Try this demo:

future.dart file:

import 'package:flutter/material.dart';
abstract class FutureBuilderRequest extends StatelessWidget{
  const FutureBuilderRequest({Key? key});

  Widget hasDataWidget(dynamic data);
  Widget noDataWidget();
  Widget noneConnectionWidget();
  Widget activeWidget();
  Widget waitingWidget();
  Future future();

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future(),
      builder: (context, AsyncSnapshot<dynamic> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return noneConnectionWidget();
          case ConnectionState.active:
            return noneConnectionWidget();
          case ConnectionState.waiting:
            return waitingWidget();

          case ConnectionState.done:
            if (snapshot.hasData) {
                return hasDataWidget(snapshot.data);
            }
        }
        // By default, show a loading spinner
        return const Center(child: CircularProgressIndicator());
      },
    );
  }
}

main.dart :

import 'package:demo/home.dart';
import 'package:flutter/material.dart';

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

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: HomeScreen(),
    );
  }
}

home.dart

import 'package:demo/future.dart';
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: Body()),
    );
  }
}


class Body extends FutureBuilderRequest {
  const Body({Key? key}) : super(key: key);

  @override
  Widget activeWidget() {
    return Container();
  }

  @override
  Future future() {
    return Future<String>.delayed(
      const Duration(seconds: 2),
          () => 'Data Loaded',
    );
  }

  @override
  Widget hasDataWidget(data) {
    return Column(
      children: [
        const Icon(
          Icons.check_circle_outline,
          color: Colors.green,
          size: 60,
        ),
        Padding(
          padding: const EdgeInsets.only(top: 16),
          child: Text('Result: ${data}'),
        )
      ],
    );
  }

  @override
  Widget noDataWidget() {
    return Container();
  }

  @override
  Widget noneConnectionWidget() {
    return Container();
  }

  @override
  Widget waitingWidget() {
    return const Center(
      child: CircularProgressIndicator(),
    );
  }
}
Related