Show CircularProgressIndicator in custom button when state app is loading using flutter provider package

36 Views Asked by At

I hope someone can help me with this :) I have a flutter app and use the provider package to manage state.

What I want to achieve: When I click on the button and delete an order I want to notify my button and provide the isLoading value. The button should show a loading spinner and lower its opacity when isLoading == true.

What is the current state: When I click on the button the order gets deleted, but neither the opacity get lowered nor the loading spinner gets displayed. Though I have access to isLoading in my button.

This is my OrderService (store):

import ...

class OrderService with ChangeNotifier {
  final supabaseClient = Supabase.instance.client;

  bool _isLoading = false;

  bool get isLoading => _isLoading;

  _toggleLoading() {
    _isLoading = !_isLoading;
    notifyListeners();
  }

  Future<void> deleteOrderWithId(int id, BuildContext context) async {
    try {
      _toggleLoading();
      await supabaseClient.from('orders').delete().eq('id', id);
    } catch (err) {
      print(err);
    } finally {
      _toggleLoading();
    }
  }

  Future<void> createOrderFromOrder(Order order, BuildContext context) async {...}
}

This is my custom button:

import ...

class TrustyButton extends StatelessWidget {
  final void Function() onTap;
  final String text;
  final ButtonType type;
  final Icon? icon;

  TrustyButton({
    super.key,
    required this.onTap,
    required this.text,
    required this.type,
    this.icon,
  });

  final Map<ButtonType, Map<String, Color>> color = {...}

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => OrderService(),
      child: Consumer<OrderService>(
        builder: (context, orderService, _) => GestureDetector(
          onTap: () {
            onTap();
          },
          child: Opacity(
            opacity: orderService.isLoading ? 0.5 : 1,
            child: Container(
              height: 56,
              decoration: BoxDecoration(
                color: color[type]!['backgroundColor']!,
                borderRadius: const BorderRadius.all(
                  Radius.circular(20),
                ),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0x15000000),
                    blurRadius: 10,
                    spreadRadius: 0,
                    offset: Offset.fromDirection(2, 2),
                  ),
                ],
              ),
              child: Center(
                child: orderService.isLoading
                    ? const CircularProgressIndicator(
                        color: Colors.white,
                      )
                    : renderButtonContent(),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
1

There are 1 best solutions below

2
Vladimir Gadzhov On BEST ANSWER

You are facing this issue, because you are creating a new instance of the OrderService inside the TrustyButton. Instead you must lift the ChangeNotifierProvider<TrustyButton>, and to use it via a Consumer inside the TrustyButton. Here is a minimal example:

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: ChangeNotifierProvider(
            create: (context) => OrderService(),
            builder: (context, _) {
              return TrustyButton(
                onTap: () => context.read<OrderService>().deleteOrderWithId(),
              );
            },
          ),
        ),
      ),
    );
  }
}

class OrderService with ChangeNotifier {
  bool _isLoading = false;

  bool get isLoading => _isLoading;

  void _toggleLoading() {
    _isLoading = !_isLoading;
    notifyListeners();
  }

  Future<void> deleteOrderWithId() async {
    try {
      _toggleLoading();
      await Future.delayed(const Duration(seconds: 5));
    } catch (err) {
      print(err);
    } finally {
      _toggleLoading();
    }
  }
}

class TrustyButton extends StatelessWidget {
  final void Function() onTap;

  TrustyButton({
    super.key,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Consumer<OrderService>(
        builder: (context, orderService, _) => GestureDetector(
          onTap: onTap,
          child: Opacity(
            opacity: orderService.isLoading ? 0.5 : 1,
            child: Container(
              height: 56,
              decoration: BoxDecoration(
                color: Colors.amberAccent,
                borderRadius: const BorderRadius.all(
                  Radius.circular(20),
                ),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0x15000000),
                    blurRadius: 10,
                    spreadRadius: 0,
                    offset: Offset.fromDirection(2, 2),
                  ),
                ],
              ),
              child: Center(
                child: orderService.isLoading
                    ? const CircularProgressIndicator(
                        color: Colors.white,
                      )
                    : Text('Delete'),
              ),
            ),
          ),
        ),
      );
  }
}