Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
190 views
in Technique[技术] by (71.8m points)

flutter - Animating between provider stream changes

I have a pageview in flutter with 5 pages, each with its own scaffold. All states are managed through providers of streams or values. I have a stream that has it's own built in method which passes InternetConnected.connected or disconnected. When the internet connection is lost, I want to load a separate UI in the specific page which shows internet connection lost instead of the widgets that were previously present in the scaffold.

How I'm doing it now (pseudo code):

class ConnectionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  final connection = Provider.of<InternetStatus>(context);

  detectConnection () {
  if (connection.InternetConnection == InternetConnection.connected)
      return Container(); // Returns UI when internet on
  else 
      return Container(); // Returns disconnected UI
  }

  return Scaffold(body: detectConnection());
}

Two Questions:

  1. I want to animate the transition between the two states ie. Connected and Disconnected with the disconnection screen flowing down from the top of the display and vice versa. What is the correct way to do that with provider state management? Right now it just rebuilds instantaneously, which is not very 'pretty'.

  2. Since Provider.of<> does not allow granular rebuilds in case the stream value changes, how would I make it so that other properties 'provided' by provider are handled better? I know about Consumer and Selector, but those are rebuilding the UI too...

Thanks in advance


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Animation

An AnimatedSwitcher widget (included in SDK, not related to Provider) might suffice to animate between your two widgets showing connected / disconnected states. (AnimatedContainer might work as well if you're just switching a color or something else in the constructor argument list of Container.)

A key is needed for the child of AnimatedSwitcher when the children are of the same class, but differ internally. If they're completely different Types, Flutter knows to animate between the two, but not if they're the same Type. (Has to do with how Flutter analyzes the widget tree looking for needed rebuilds.)

Rebuild Only Affected Widgets

In the example below, the YellowWidget isn't being rebuilt, and neither is its parent. Only the Consumer<InternetStatus> widget is being rebuilt when changing from Connected to Disconnected statuses in the example.

I'm not an expert on Provider and I find it easy to make mistakes in knowing which Provider / Consumer / Selector / watcher to use to avoid unnecessary rebuilds. You might be interested in other State Management solutions like Get, or RxDart+GetIt, etc. if Provider doesn't click for you.

Note: an extra Builder widget is used as parent to the child of ChangeNotifierProvider, to make everything underneath a child. This allows InheritedWidget to function as intended (the base upon which Provider is built). Otherwise, the child of ChangeNotifierProvider would actually share its context and be its sibling, not descendant.

i.e. They'd both get the context shown here:

class ProviderGranularPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

This is also a tricky nuance of Flutter. If you wrap your entire MaterialApp or MyApp widget in Provider, this extra Builder is obviously unnecessary.

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

class InternetStatus extends ChangeNotifier {
  bool connected = true;

  void setConnect(bool _connected) {
    connected = _connected;
    notifyListeners();
  }
}

/// Granular rebuilds using Provider package
class ProviderGranularPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<InternetStatus>(
      create: (_) => InternetStatus(),
      child: Builder(
        builder: (context) {
          print('Page (re)built');
          return SafeArea(
            child: Scaffold(
              body: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Expanded(
                    flex: 3,
                    child: Consumer<InternetStatus>(
                      builder: (context, inetStatus, notUsed) {
                        print('status (re)built');
                        return AnimatedSwitcher(
                          duration: Duration(seconds: 1),
                          child: Container(
                              key: getStatusKey(context),
                              alignment: Alignment.center,
                              color: getStatusColor(inetStatus),
                              child: getStatusText(inetStatus.connected)
                          ),
                        );
                      },
                    ),
                  ),
                  Expanded(
                    flex: 3,
                    child: YellowWidget(),
                  ),
                  Expanded(
                    flex: 1,
                    child: Container(
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          RaisedButton(
                            child: Text('Connect'),
                            onPressed: () => setConnected(context, true),
                          ),
                          RaisedButton(
                            child: Text('Disconnect'),
                            onPressed: () => setConnected(context, false),
                          )
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  /// Show other ways to access Provider State, using context & Provider.of
  Key getStatusKey(BuildContext context) {
    return ValueKey(context.watch<InternetStatus>().connected);
  }

  void setConnected(BuildContext context, bool connected) {
    Provider.of<InternetStatus>(context, listen: false).setConnect(connected);
  }

  Color getStatusColor(InternetStatus status) {
    return status.connected ? Colors.blue : Colors.red;
  }

  Widget getStatusText(bool connected) {
    String _text = connected ? 'Connected' : 'Disconnected';
    return Text(_text, style: TextStyle(fontSize: 25));
  }
}

class YellowWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Yellow was (re)built');
    return Container(
      color: Colors.yellow,
      child: Center(
        child: Text('This should not rebuild'),
      ),
    );
  }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...