An all-in-one Fllutter package for state management, reactive objects, animations, effects, timed widgets etc.

Overview

Frideos pub package

An all-in-one package for state management, streams and BLoC pattern, animations and timed widgets, effects.

Contents

1. State management

Getting started

  • StreamedValue
  • AppStateModel
  • AppStateProvider
  • ValueBuilder
  • StreamedWidget

Specialized StreamedObjects

  • StreamedList
  • StreamedMap
  • HistoryObject
  • MemoryValue
  • StreamedTransformed
Other
  • FuturedWidget
  • ReceiverWidget
  • StreamedSender, ListSender, and MapSender

2. Animations and timing

  • AnimationTween
  • AnimationCreate
  • AnimationCurved
  • CompositeItem
  • AnimationComposite
  • CompositeCreate
  • ScenesObject
  • ScenesCreate
  • StagedObject
  • StagedWidget
  • WavesWidget
  • ScrollingText

3. Effects

  • LinearTransition
  • CurvedTransition
  • FadeInWidget
  • FadeOutWidget
  • BlurWidget
  • BlurInWidget
  • BlurOutWidget
  • AnimatedBlurWidget

Articles and examples

  • A book trailer with Flutter web

  • Quiz game: a simple trivia game built with Flutter and this package. You can read an article about this example here: https://medium.com/flutter-community/flutter-how-to-build-a-quiz-game-596d0f369575

  • Todo App: an implementation of the Todo App of the Flutter Architecture Samples repository using this package.

  • Frideos examples: an example app to show how to use some features of this library.

    • Streamed objects
    • Streamed collections
    • TimerObject: a simple stopwatch
    • StagedObject
    • StagedWidget
    • AnimatedObject
    • Multiple selection and tunnel pattern (to share data between two blocs)
    • LinearTransition
    • CurvedTransition
    • Blur (fixed, in, out, animated)
    • WavesWidget
    • Sliders
    • Products catalog
  • Dynamic fields validation: a Flutter example on how to validate dynamically created fields with the BLoC pattern and this package.

  • Theme changer: a simple starter app with a drawer, app state management, dynamic theme changer and persistent theme using the sharedpreferences.

  • Counter: a simple app using the BLoC pattern showing a counter implemented with this library.

  • Blood pressure: an example of a medical app built with Flutter for the classification of the arterial blood pressure.

  • Pair game: a simple pair game (multiple selections, animations, tunnel pattern).

Dependencies

State management

Getting started

The core of the package consists of classes that implement the StramedObject interface:

///
/// Interface for all the StreamedObjects
///
abstract class StreamedObject<T> {
  /// Getter for the stream exposed by the classes that implement
  /// the StreamedObject interface.
  Stream<T> get outStream;

  /// Getter for the last value emitted by the stream
  T get value;
}
  • HistoryObject
  • MemoryValue
  • StreamedList
  • StreamedMap
  • StreamedTransformed
  • StreamedValue

These objects are then used (e.g. in classes extending the AppStateModel interface) in combination with the ValueBuilder widget (or StreamedWidget/StreamBuilder), to make the UI reactive to their changes.

StreamedValue

The StreamedValue is the simplest class that implements this interface. Every time a new value is set, this is compared to the latest one and if it is different, it is sent to stream. Used in tandem with ValueBuilder (or StreamedWidget/StreamBuilder) it automatically triggers the rebuild of the widgets returned by its builder.

So for example, instead of:

counter += 1;
stream.sink.add(counter);

It becomes just:

counter.value += 1;

It can be used even with StreamedWidget and StreamBuilder by using its stream getter outStream.

N.B. when the type is not a basic type (e.g int, double, String etc.) and the value of a property of the object is changed, it is necessary to call the refresh method to update the stream.

Example

// In the BLoC
final count = StreamedValue<int>(initialData: 0);

incrementCounter() {
  count.value += 2.0;
}

// View
ValueBuilder<int>(
  streamed: bloc.count, // no need of the outStream getter with ValueBuilder
  builder: (context, snapshot) =>
    Text('Value: ${snapshot.data}'),
  noDataChild: Text('NO DATA'),
),
RaisedButton(
    color: buttonColor,
    child: Text('+'),
    onPressed: () {
      bloc.incrementCounter();
    },
),

// As an alternative:
//
// StreamedWidget<int>(    
//    stream: bloc.count.outStream,
//    builder: (context, snapshot) => Text('Value: ${snapshot.data}'),
//    noDataChild: Text('NO DATA'),
//),

If on debugMode, on each update the timesUpdated increases showing how many times the value has been updated.

N.B. For collections use StreamedList and StreamedMap instead.

AppStateModel and AppStateProvider

These reactive objects can be used in classes extending the AppStateModel interface, and provided to the widgets tree using the AppStateProvider widget.

From the "theme changer" example:

1. Create a model for the app state:

class AppState extends AppStateModel {
  List<MyTheme> themes;
  StreamedValue<MyTheme> currentTheme;

  AppState() {
    print('-------APP STATE INIT--------');

    themes = List<MyTheme>();

    themes.addAll([
      MyTheme(
        name: 'Default',
        brightness: Brightness.light,
        backgroundColor: Colors.blue[50],
        scaffoldBackgroundColor: Colors.blue[50],
        primaryColor: Colors.blue,
        primaryColorBrightness: Brightness.dark,
        accentColor: Colors.blue[300],
      ),
      MyTheme(
        name: 'Teal',
        brightness: Brightness.light,
        backgroundColor: Colors.teal[50],
        scaffoldBackgroundColor: Colors.teal[50],
        primaryColor: Colors.teal[600],
        primaryColorBrightness: Brightness.dark,
        accentColor: Colors.teal[300],
      ),
      MyTheme(
        name: 'Orange',
        brightness: Brightness.light,
        backgroundColor: Colors.orange[50],
        scaffoldBackgroundColor: Colors.orange[50],
        primaryColor: Colors.orange[600],
        primaryColorBrightness: Brightness.dark,
        accentColor: Colors.orange[300],
      ),
    ]);

    currentTheme = StreamedValue();
  }

  void setTheme(MyTheme theme) => currentTheme.value = theme;    
  

  @override
  void init() => currentTheme.value = themes[0];    
 

  @override
  dispose() {
    print('---------APP STATE DISPOSE-----------');
    currentTheme.dispose();
  }
}

2. Wrap the MaterialApp in the AppStateProvider:

void main() => runApp(App());

class App extends StatefulWidget {
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  AppState appState;

  @override
  void initState() {
    super.initState();
    appState = AppState();
  }

  @override
  Widget build(BuildContext context) {
    return AppStateProvider<AppState>(
      appState: appState,
      child: MaterialPage(),
    );
  }
}

3. Consume the data:

class MaterialPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var theme = AppStateProvider.of<AppState>(context).currentTheme;

    return ValueBuilder<MyTheme>(
        streamed: theme,
        builder: (context, snapshot) {
          return MaterialApp(
              title: "Theme and drawer starter app",
              theme: _buildThemeData(snapshot.data),
              home: HomePage(),
            );
        },
    );
  }

  _buildThemeData(MyTheme appTheme) {
    return ThemeData(
      brightness: appTheme.brightness,
      backgroundColor: appTheme.backgroundColor,
      scaffoldBackgroundColor: appTheme.scaffoldBackgroundColor,
      primaryColor: appTheme.primaryColor,
      primaryColorBrightness: appTheme.primaryColorBrightness,
      accentColor: appTheme.accentColor,
    );
  }
}

4. Change the data (using a stream):

class SettingsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = AppStateProvider.of<AppState>(context);

    _buildThemesList() {
      return appState.themes.map((MyTheme appTheme) {
        return DropdownMenuItem<MyTheme>(
          value: appTheme,
          child: Text(appTheme.name, style: TextStyle(fontSize: 14.0)),
        );
      }).toList();
    }

    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text(
          "Settings",
        ),
      ),
      body: Container(
        padding: EdgeInsets.all(8.0),
        child: Column(
          children: <Widget>[
            Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text(
                    'Choose a theme:',
                    style: TextStyle(fontWeight: FontWeight.w500),
                  ),
                ),
                ValueBuilder<MyTheme>(
                  streamed: appState.currentTheme,
                  builder: (context, snapshot) {
                    return DropdownButton<MyTheme>(
                      hint: Text("Status"),
                      value: snapshot.data,
                      items: _buildThemesList(),
                      onChanged: appState.setTheme,
                    );
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

ValueBuilder

ValueBuilder extends the [StreamBuilder] widget providing some callbacks to handle the state of the stream and returning a [Container] if noDataChild is not provided, in order to avoid checking snapshot.hasData.

N.B. To use when there is no need to receive a null value.

It takes as a streamed parameter an object implementing the [StreamedObject] interface and triggers the rebuild of the widget whenever the stream emits a new event.

Usage

ValueBuilder<String>(
  streamed: streamedValue,
  builder: (context, snasphot) => Text(snasphot.data),
  initialData: // Data to provide for the initial snapshot
  noDataChild: // Widget to show when the stream has no data
  onNoData: () => // or Callback
  errorChild: // Widget to show on error
  onError: (error) => // or Callback
)

If no [noDataChild] widget or [onNoData] callback is provided then a [Container] is returned.

If no [errorChild] widget or no [onError] callback is provided then a [Container] is returned.

N.B. The callbacks are executed only if their respective child is not provided.

StreamedWidget

StreamedWidget extends the [StreamBuilder] widget providing some callbacks to handle the state of the stream and returning a [Container] if noDataChild is not provided, in order to avoid checking snapshot.hasData.

N.B. To use when there is no need to receive a null value.

It takes as a stream parameter a [Stream] and triggers the rebuild of the widget whenever the stream emits a new event.

If no [noDataChild] widget or [onNoData] callback is provided then a [Container] is returned.

If no [errorChild] widget or no [onError] callback is provided then a [Container] is returned.

Usage

StreamedWidget<String>(
  stream: stream,
  builder: (context, snasphot) => Text(snasphot.data),
  noDataChild: // Widget to show when the stream has no data
  onNoData: () => // or Callback
  errorChild: // Widget to show on error
  onError: (error) => // or Callback
)

In case of an object implementing the StreamedObject interface (eg. StreamedValue, StreameList etc.):

StreamedWidget<String>(
  stream: streamedObject.outStream, // outStream getter
  builder: (context, snasphot) => Text(snasphot.data),
  noDataChild: // Widget to show when the stream has no data
  onNoData: () => // or Callback
  errorChild: // Widget to show on error
  onError: (error) => // or Callback
)

N.B. The callbacks are executed only if their respective child is not provided.

Specialized StreamedObjects

StreamedList

This class has been created to work with lists. It works like StreamedValue.

To modify the list (e.g. adding items) and update the stream automatically use these methods:

  • AddAll
  • addElement
  • clear
  • removeAt
  • removeElement
  • replace
  • replaceAt

For other direct actions on the list, to update the stream call the refresh method instead.

Usage

e.g. adding an item:

 streamedList.addElement(item);

it is the same as:

  streamedList.value.add(item);
  streamedList.refresh();

From the StreamedList example:

  final streamedList = StreamedList<String>();


  // Add to the streamed list the string from the textfield
  addText() {
    streamedList.addElement(streamedText.value);

    // Or, as an alternative:
    // streamedList.value.add(streamedText.value);
    // streamedList.refresh(); // To refresh the stream with the new value
  }

StreamedMap

This class has been created to work with maps, it works like StreamedList.

To modify the list (e.g. adding items) and update the stream automatically use these methods:

  • addKey
  • removeKey
  • clear

For other direct actions on the map, to update the stream call the refresh method instead.

Usage

e.g. adding a key/value pair:

  streamedMap.addKey(1, 'first');

it is the same as:

   streamedMap.value[1] = 'first';
   streamedList.refresh();

From the streamed map example:

  final streamedMap = StreamedMap<int, String>();


  // Add to the streamed map the key/value pair put by the user
  addText() {
    var key = int.parse(streamedKey.value);
    var value = streamedText.value;

    streamedMap.addKey(key, value);

    // Or, as an alternative:
    //streamedMap.value[key] = value;
    //streamedMap.refresh();
  }

MemoryValue

The MemoryValue has a property to preserve the previous value. The setter checks for the new value, if it is different from the one already stored, this one is given oldValue before storing and streaming the new one.

Usage

final countMemory = MemoryValue<int>();

countMemory.value // current value
couneMemory.oldValue // previous value

HistoryObject

Extends the MemoryValue class, adding a StreamedList. Useful when it is need to store a value in a list.

final countHistory = HistoryObject<int>();

incrementCounterHistory() {
  countHistory.value++;
}

saveToHistory() {
  countHistory.saveValue();
}

StreamedTransformed

A particular class the implement the StreamedObject interface, to use when there is the need of a StreamTransformer (e.g. stream transformation, validation of input fields, etc.).

Usage

From the StreamedMap example:

// In the BLoC class
final streamedKey = StreamedTransformed<String, int>();



// In the constructor of the BLoC class
streamedKey.setTransformer(validateKey);



// Validation (e.g. in the BLoC or in a mixin class)
final validateKey =
      StreamTransformer<String, int>.fromHandlers(handleData: (key, sink) {
    var k = int.tryParse(key);
    if (k != null) {
      sink.add(k);
    } else {
      sink.addError('The key must be an integer.');
    }
  });


// In the view:
StreamBuilder<int>(
            stream: bloc.streamedKey.outTransformed,
            builder: (context, snapshot) {
              return Column(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(
                      vertical: 12.0,
                      horizontal: 20.0,
                    ),
                    child: TextField(
                      style: TextStyle(
                        fontSize: 18.0,
                        color: Colors.black,
                      ),
                      decoration: InputDecoration(
                        labelText: 'Key:',
                        hintText: 'Insert an integer...',
                        errorText: snapshot.error,
                      ),
                      // To avoid the user could insert text use the TextInputType.number
                      // Here is commented to show the error msg.
                      //keyboardType: TextInputType.number,
                      onChanged: bloc.streamedKey.inStream,
                    ),
                  ),
                ],
              );
            }),

Other

FuturedWidget

FuturedWidget is a wrapper for the [FutureBuilder] widget. It provides some callbacks to handle the state of the future and returning a [Container] if onWaitingChild is not provided, in order to avoid checking snapshot.hasData.

Usage

FuturedWidget<String>(
  future: future,
  builder: (context, snasphot) => Text(snasphot.data),
  initialData: // Data to provide if the snapshot is null or still not completed
  waitingChild: // Widget to show on waiting
  onWaiting: () => // or Callback
  errorChild: // Widget to show on error
  onError: (error) => // or Callback
)

If no [onWaitingChild] widget or [onWaiting] callback is provided then a [Container] is returned.

If no [errorChild] widget or no [onError] callback is provided then a [Container] is returned.

N.B. The callbacks are executed only if their respective child is not provided.

ReceiverWidget

Used with a StreamedValue when the type is a widget to directly stream a widget to the view. Under the hood a StreamedWidget handles the stream and shows the widget.

Usage

ReceiverWidget(stream: streamedValue.outStream),

StreamedSender

Used to make a one-way tunnel beetween two blocs (from blocA to a StremedValue on blocB).

Usage

  1. Define an object that implements the StreamedObject interface in the blocB (e.g. a StreamedValue):

final receiverStr = StreamedValue<String>();
  1. Define a StreamedSender in the blocA:

final tunnelSenderStr = StreamedSender<String>();
  1. Set the receiver in the sender on the class the holds the instances of the blocs:

blocA.tunnelSenderStr.setReceiver(blocB.receiverStr);
  1. To send data from blocA to blocB then:

tunnelSenderStr.send("Text from blocA to blocB");

ListSender and MapSender

Like the StreamedSender, but used with collections.

Usage

  1. Define a StreamedList or StreamedMap object in the blocB

final receiverList = StreamedList<int>();
final receiverMap = StreamedMap<int, String>();
  1. Define a ListSender/MapSender in the blocA

final tunnelList = ListSender<int>();
final tunnelMap = MapSender<int, String>();
  1. Set the receiver in the sender on the class the holds the instances of the blocs

blocA.tunnelList.setReceiver(blocB.receiverList);
blocA.tunnelMap.setReceiver(blocB.receiverMap);
  1. To send data from blocA to blocB then:

tunnelList.send(list);
tunnelMap.send(map);

Animations

AnimationTween

anim = AnimationTween<double>(
        duration: Duration(milliseconds: 120000),
        setState: setState,
        tickerProvider: this,
        begin: 360.0,
        end: 1.0,
        onAnimating: _onAnimating,
);

opacityAnim = AnimationTween<double>(
  begin: 0.5,
  end: 1.0,
  controller: anim.baseController,
);

growAnim = AnimationTween<double>(
  begin: 1.0,
  end: 30.0,
  controller: anim.baseController,
);

// Play animation
anim.forward();


// Called on each frame
void _onAnimating(AnimationStatus status) {   
  if (status == AnimationStatus.completed) {
    anim.reverse();
  }
  if (status == AnimationStatus.dismissed) {
    anim.forward();
  }
}


// Example
Opacity(
  opacity: opacityAnim.value,
  child: Container(
    alignment: Alignment.center,
    height: 100 + growAnim.value,
    width: 100 + growAnim.value,
    decoration: BoxDecoration(
      color: Colors.blue,
        boxShadow: [
          BoxShadow(blurRadius: anim.value),
        ],        
  ),
),

AnimationCreate

 AnimationCreate<double>(
        begin: 0.1,
        end: 1.0,
        curve: Curves.easeIn,
        duration: 1000,
        repeat: true,
        reverse: true,
        builder: (context, anim) {          
          return Opacity(
                opacity: anim.value,
                child: Text(
                  'Loading...',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 26.0,
                  ),
                ),
              );

AnimationCurved

 circleAnim = AnimationCurved<double>(
        duration: Duration(milliseconds: 7000),
        setState: setState,
        tickerProvider: this,
        begin: 360.0,
        end: 1.0,
        curve: Curves.bounceInOut,
        onAnimating: _onAnimating,
);

circleAnim.forward();

CompositeItem and AnimationComposite

    compAnim = AnimationComposite(
        duration: Duration(milliseconds: 1750),
        setState: setState,
        tickerProvider: this,
        composite: {
          'background': CompositeItem<Color>(
              begin: Colors.amber, end: Colors.blue, curve: Curves.elasticIn),
          'grow': CompositeItem<double>(begin: 1.0, end: 40.0),
          'rotate': CompositeItem<double>(
              begin: math.pi / 4, end: math.pi, curve: Curves.easeIn),
          'color': CompositeItem<Color>(
              begin: Colors.green, end: Colors.orange, curve: Curves.elasticIn),
          'shadow': CompositeItem<double>(begin: 5.0, end: 30.0),
          'rounded': CompositeItem<double>(
            begin: 0.0,
            end: 150.0,
            curve: Curves.easeIn,
          )
        });

    movAnim = AnimationComposite(
        duration: Duration(milliseconds: 1750),
        setState: setState,
        tickerProvider: this,
        composite: {
          'upper': CompositeItem<Offset>(
              begin: Offset(-60.0, -30.0),
              end: Offset(80.0, 15.0),
              curve: Curves.easeIn),
          'lower': CompositeItem<Offset>(
              begin: Offset(-80.0, 0.0),
              end: Offset(70.0, -25.0),
              curve: Curves.easeInCirc),
        });



// Example
Transform.translate(
  offset: movAnim.value('lower'),
  child: Transform.rotate(
    angle: compAnim.value('rotate'),
    child: Container(
      alignment: Alignment.center,
      height: 100 + compAnim.value('grow'),
      width: 100 + compAnim.value('grow'),
      decoration: BoxDecoration(
        color: compAnim.value('color'),
        boxShadow: [
          BoxShadow(blurRadius: compAnim.value('shadow')),
          ],
        borderRadius: BorderRadius.circular(
          compAnim.value('rounded'),
          ),
       ),
    ),
  ),
),

CompositeCreate

enum AnimationType {
  fadeIn,  
  scale,  
  fadeOut,  
}


 @override
  Widget build(BuildContext context) {
    return CompositeCreate(
      duration: duration,
      repeat: false,
      compositeMap: {
        AnimationType.fadeIn: CompositeItem<double>(
          begin: 0.2,
          end: 1.0,
          curve: const Interval(
            0.0,
            0.2,
            curve: Curves.linear,
          ),
        ),
        AnimationType.scale: CompositeItem<double>(
          begin: reverse ? 0.8 : 1.0,
          end: reverse ? 1.0 : 0.8,
          curve: const Interval(
            0.2,
            0.6,
            curve: Curves.linear,
          ),
        ),
        AnimationType.fadeOut: CompositeItem<double>(
          begin: 1.0,
          end: 0.0,
          curve: Interval(
            0.7,
            0.8,
            curve: Curves.linear,
          ),
        ),
      },
      onCompleted: onCompleted,
      builder: (context, comp) {
        return Transform.scale(
          scale: comp.value(AnimationType.scale),
          child: Opacity(
                  opacity: comp.value(AnimationType.fadeIn),
                  child: Opacity(
                    opacity: comp.value(AnimationType.fadeOut),
                    child: Text(
                      'Text',

ScenesObject

A complex class to hadle the rendering of scenes over the time. It takes a collection of "Scenes" and triggers the visualization of the widgets at a given time (relative o absolute timing). For example to make a demostration on how to use an application, showing the widgets and pages along with explanations.

Every scene is handled by using the Scene class:

 class Scene {
   Widget widget;
   int time; // milliseconds
   Function onShow = () {};
   Scene({this.widget, this.time, this.onShow});
 }
N.B. The onShow callback is used to trigger an action when the scene shows

Usage

From the ScenesObject example:

1 - Declare a list of scenes

final ScenesObject scenesObject = ScenesObject();

2 - Add some scenes

scenes.addAll([
  Scene(
    time: 4000,
    widget: SingleScene(text: 'Scene 1', color: Colors.blueGrey),
    onShow: () => print('Showing scene 1'),
  ),
  Scene(
    time: 4000,
    widget: SingleScene(text: 'Scene 2', color: Colors.orange),
    onShow: () => print('Showing scene 1'),
  )
]);


// The singleScene widget:

class SingleScene extends StatelessWidget {
  const SingleScene({Key key, this.text, this.color}) : super(key: key);

  final String text;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      color: color,
      child: Text(text),
    );
  }
}

3 - Setup the ScenesObject and play the scenes

scenesObject
  ..setScenesList(scenes)
  ..setCallback(() => print('Called on start'))
  ..setOnEndCallback(scenesObject.startScenes); // Replay the scenes at the end

// For e.g. on tap on a button:
scenesObject.startScenes();

ScenesCreate

This widget uses a [ScenesObject] for the timing of the widgets visualization.

It takes as a parameter a List and plays every [Scene].

By default to change the stage is used the relative time, so the time parameter of the [Scene] indicates how much time the stage will lasts. Instead, to specify the absolute time, set to true the [absoluteTiming] flag, in this case the time parameter indicates the absolute time when to show the scene.

The [onStart] is used to call a function when the ScenesObject begins to play the stages.

The [onEnd] callback is called at the end of the last stage when the timeing is relative (the [absoluteTiming] flag is set to false).

Usage

From the ScenesObject example:

ScenesCreate(
 scenes: [
   Scene(
       widget: SingleScene(
         color: Colors.white,
         text: 'Scene 1',
       ),
       time: 3500,
       onShow: () {
         print('Showing scene 1');
       }),
   Scene(
       widget: SingleScene(
         color: Colors.blue,
         text: 'Scene 2',
       ),
       time: 3500,
       onShow: () {
         print('Showing scene 2');
       }),
   Scene(
       widget: SingleScene(
         color: Colors.brown,
         text: 'Scene 3',
       ),
       time: 3500,
       onShow: () {
         print('Showing scene 3');
       }),
 ],
 onStart: () => print('Start playing scenes!'),
 onEnd: () => print('End playing scenes!'),
),


// The singleScene widget:

class SingleScene extends StatelessWidget {
 const SingleScene({Key key, this.text, this.color}) : super(key: key);

 final String text;
 final Color color;

 @override
 Widget build(BuildContext context) {
   return Container(
     alignment: Alignment.center,
     color: color,
     child: Text(text),
   );
 }
}

StagedObject

A complex class to hadle the rendering of widgets over the time. It takes a collection of "Stages" and triggers the visualization of the widgets at a given time (relative o absolute timing). For example to make a demostration on how to use an application, showing the widgets and pages along with explanations.

StagedObject

Every stage is handled by using the Stage class:

class Stage {
  Widget widget;
  int time; // milliseconds
  Function onShow = () {};
  Stage({this.widget, this.time, this.onShow});
}
N.B. The onShow callback is used to trigger an action when the stage shows

Usage

From the StagedObject example:

  1. Declare a map <int, Stage>

    Here the map is in the view and is set in the BLoC class by the setStagesMap.
Map<int, Stage> get stagesMap => <int, Stage>{
  0: Stage(
      widget: Container(
        width: 200.0,
        height: 200.0,
        color: Colors.indigo[200],
        alignment: Alignment.center,
        key: Key('0'),
        child: ScrollingText(
          text:
            'This stage will last 8 seconds. By the onShow call back it is possibile to assign an action when the widget shows.',
          scrollingDuration: 2000,
          style: TextStyle(
            color: Colors.blue,
            fontSize: 18.0,
            fontWeight: FontWeight.w500)),
        ),
      time: 8000,
      onShow: () {}),
  1: Stage(
      widget: Container(
        width: 200.0,
        height: 200.0,
        color: Colors.indigo[200],
        alignment: Alignment.center,
        key: Key('00'),
        child: ScrollingText(
              text: 'The next widgets will cross      fade.',
              scrollingDuration: 2000,
            ),
          ),
      time: 8000,
      onShow: () {}),

}
  1. In the BLoC

  final text = StreamedValue<String>();
  final staged = StagedObject();


  // The map can be set through the constructor of the StagedObject
  // or by the setStagesMap method like in this case.
  setMap(Map<int, Stage> stagesMap) {
    staged.setStagesMap(stagesMap);
  }


  // This method is then called from a button in the view
  start() {
    if (staged.getMapLength() > 0) {
      staged.setCallback(sendNextStageText);
      staged.startStages();
    }
  }

  // By this method we get the next stage to show it
  // in a little box below the current stage
  sendNextStageText() {
    var nextStage = staged.getNextStage();
    if (nextStage != null) {
      text.value = "Next stage:";
      widget.value = nextStage.widget;
      stage.value = StageBridge(
          staged.getStageIndex(), staged.getCurrentStage(), nextStage);
    } else {
      text.value = "This is the last stage";
      widget.value = Container();
    }
  }
  1. In the view:

  // Setting the map in the build method
  StagedObjectBloc bloc = BlocProvider.of(context);
  bloc.setMap(stagesMap);


  // To show the current widget on the view using the ReceiverWidget.
  // As an alternative it can be used the StreamedWidget/StreamBuilder.
  ReceiverWidget(
    stream: bloc.staged.widgetStream,
  ),

StagedWidget

StagedWidget

Usage

  1. Declare a map <int, Stage>

    Here the map is in the view and is set in the BLoC class by the setStagesMap.
Map<int, Stage> get stagesMap => <int, Stage>{
  0: Stage(
      widget: Container(
        width: 200.0,
        height: 200.0,
        color: Colors.indigo[200],
        alignment: Alignment.center,
        key: Key('0'),
        child: ScrollingText(
          text:
            'This stage will last 8 seconds. By the onShow call back it is possibile to assign an action when the widget shows.',
          scrollingDuration: 2000,
          style: TextStyle(
            color: Colors.blue,
            fontSize: 18.0,
            fontWeight: FontWeight.w500)),
        ),
      time: 8000,
      onShow: () {}),
  1: Stage(
      widget: Container(
        width: 200.0,
        height: 200.0,
        color: Colors.indigo[200],
        alignment: Alignment.center,
        key: Key('00'),
        child: ScrollingText(
              text: 'The next widgets will cross      fade.',
              scrollingDuration: 2000,
            ),
          ),
      time: 8000,
      onShow: () {}),

}
  1. In the view:

StagedWidget(
  stagesMap: stagesMap,
  onStart: // function to call,
  onEnd: () {
    // Function to call at the end of the last stage
    // (only if relative timing):
    // e.g. Navigator.pop(context);
  }),

WavesWidget

Usage

WavesWidget(
  width: 128.0,
  height: 128.0,
  color: Colors.red,
  child: Container(
    color: Colors.red[400],
 ),

ScrollingText

Usage

ScrollingText(
 text: 'Text scrolling (during 8 seconds).',
 scrollingDuration: 2000, // in milliseconds
 style: TextStyle(color: Colors.blue,
    fontSize: 18.0, fontWeight: FontWeight.w500),
),

Effects

LinearTransition

Linear cross fading transition between two widgets, it can be used with the StagedObject.

LinearTransition

Usage

LinearTransition(
  firstWidget: Container(height: 100.0, width: 100.0,
        color: Colors.blue),
  secondWidget: Container(height: 100.0, width: 100.0,
        color: Colors.lime),
  transitionDuration: 4000,
),

CurvedTransition

Cross fading transition between two widgets. This uses the Flutter way to make an animation.

CurvedTransition

Usage

CurvedTransition(
  firstWidget: Container(height: 100.0, width: 100.0,
     color: Colors.blue),
  secondWidget: Container(height: 100.0, width: 100.0,
     color: Colors.lime),
  transitionDuration: 4000,
  curve: Curves.bounceInOut,
),

FadeInWidget

Usage

FadeInWidget(
  duration: 7000,
  child: ScrollingText(
      text: 'Fade in text',
      scrollingDuration: 2000,
      style: TextStyle(
        color: Colors.blue,
        fontSize: 94.0,
        fontWeight: FontWeight.w500,
      ),
    ),
),

FadeOutWidget

Usage

FadeOutWidget(
  duration: 7000,
  child: ScrollingText(
      text: 'Fade out text',
      scrollingDuration: 2000,
      style: TextStyle(
        color: Colors.blue,
        fontSize: 94.0,
        fontWeight: FontWeight.w500,
      ),
    ),
),

BlurWidget

Blur

Usage

BlurWidget(
  sigmaX: 2.0,
  sigmaY: 3.0,
  child: Text('Fixed blur')
)

BlurInWidget

Usage

BlurInWidget(
  initialSigmaX: 2.0,
  initialSigmaY: 12.0,
  duration: 5000,
  refreshTime: 20,
  child: Text('Blur out'),
)

BlurOutWidget

Usage

BlurOutWidget(
  finalSigmaX: 2.0,
  finalSigmaY: 12.0,
  duration: 5000,
  refreshTime: 20,
  child: Text('Blur out'),
)

AnimatedBlurWidget

Usage

AnimatedBlurWidget(
  initialSigmaX: 2.0,
  initialSigmaY: 3.0,
  finalSigmaX: 2.0,
  finalSigmaY: 3.0,
  duration: 5000,
  reverseAnimation: true,
  loop: true,
  refreshTime: 20,
  child: Text('Fixed blur')
)
Comments
  • [Question] Migration from 0.1.3 to 0.4.1

    [Question] Migration from 0.1.3 to 0.4.1

    Hi Frideos,

    I've been using the 0.1.3 for some time now. I started looking at the 0.4.1 but faced an issue you can maybe help me with. to demonstrate this I have reimplemented the default flutter app with a BLOC that looks like this:

    class HomePageBloc {
      final StreamedValue<int> _counter;
    
      HomePageBloc() : _counter = StreamedValue<int>(initialData: 0);
    
      Stream<int> get counter => _counter.outStream;
    
      void incrementCounter() {
        _counter.value++;
      }
    }
    

    Unforunatelly the counter 0 never show on the widget. the initial Data is always null. Is this expected?

                StreamedWidget(
                  stream: _bloc.counter,
                  builder: (context, snapshot) {
                    if (snapshot.data == null) return Container();
                    return Text(
                      '${snapshot.data}',
                      style: Theme.of(context).textTheme.display1,
                    );
                  },
                ),
    

    the initial data is not available on the stream. Any sugestion?

    Regards,

    Alex.

    question 
    opened by AlexandreRoba 10
  • [Question] Dependencies on shared_preferences

    [Question] Dependencies on shared_preferences

    Hi Frideos,

    Why the dependency to shared_preference? This dependency will create problems as there are pods and Android packages behind this (unless mistaken).... I would strongly encourage you to split the implementation that depends on it to another package... frideos is something that could make send to other framework (dart web, backend... ) That might not support shared preferences... What do you think?

    Alex.

    enhancement 
    opened by developerspikaway 5
  • Logging on close

    Logging on close

    Hello, Is there a way to prevent the dispose to log at every call such as:

    flutter: ---------- Closing Stream ------ type: int
    flutter: ---------- Closing Stream ------ type: String
    flutter: ---------- Closing Stream ------ type: Error
    flutter: ---------- Closing Stream ------ type: bool
    flutter: ---------- Closing Stream ------ type: String
    flutter: ---------- Closing Stream ------ type: Error
    flutter: ---------- Closing Stream ------ type: String
    flutter: ---------- Closing Stream ------ type: bool
    flutter: ---------- Closing Stream ------ type: String
    flutter: ---------- Closing Stream ------ type: PhoneInfo
    flutter: ---------- Closing Stream ------ type: PhoneInfo
    flutter: ---------- Closing Stream ------ type: String
    flutter: ---------- Closing Stream ------ type: bool
    

    Thanks,

    Alex.

    opened by AlexandreRoba 2
  • Black screen before opening the first screen

    Black screen before opening the first screen

    Hello, I am trying to build the app with custom theme using ValueBuilder and AppStateProvider.

    Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.14.6 18G103, locale en-VN) • Flutter version 1.12.13+hotfix.5 at /Users/thanhla/flutter • Framework revision 27321ebbad (6 weeks ago), 2019-12-10 18:15:01 -0800 • Engine revision 2994f7e1e6 • Dart version 2.7.0

    But my app appear black screen before opening the first screen.

        final theme = AppStateProvider.of<AppState>(context).currentTheme;
    
        return ValueBuilder<MyTheme>(
          streamed: theme,
          builder: (context, snapshot) => MaterialApp(
            title: 'Flutter Demo',
            theme: _buildThemeData(snapshot.data),
            onGenerateRoute: generateRoute,
            debugShowCheckedModeBanner: false,
            navigatorKey: navigatorKey,
            initialRoute: Routes.home,
          ),
        );
    

    Please check my demo code, thank you! quiz.zip

    good first issue 
    opened by docbohanh 1
  • not able to run app on iOS

    not able to run app on iOS

    I did add flutter_flavorizr after creating the project, nonetheless, the project is quite new, max one month old. When trying to run it in an iOS simulator I get this error:

    Launching lib/main-dev.dart on iPhone SE (2nd generation) in debug mode...

    Running Xcode build...

    Xcode build done. 3.7s Failed to build iOS app Error output from Xcode build: ↳ ** BUILD FAILED **

    Xcode's output: ↳ diff: /Podfile.lock: No such file or directory diff: /Manifest.lock: No such file or directory error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. note: Using new build system note: Building targets in parallel note: Planning build note: Constructing build description warning: Capabilities for Signing & Capabilities may not function correctly because its entitlements use a placeholder team ID. To resolve this, select a development team in the Runner editor. (in target 'Runner' from project 'Runner')

    Could not build the application for the simulator. Error launching application on iPhone SE (2nd generation).

    opened by rubinbasha 0
  • Seem to be not supported web

    Seem to be not supported web

    Hello frideos team,

    I try to build web application, but it stuck with error:

    ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
    The following UnsupportedError was thrown building _InheritedState:
    Unsupported operation: Platform._operatingSystem
    
    The relevant error-causing widget was:
      _InheritedState
      file:///Users/macbook/flutter/.pub-cache/hosted/pub.dartlang.org/frideos-1.0.0+1/lib/src/app_state_provider.dart:51:7
    

    Please help me check it again, thank you!

    good first issue 
    opened by docbohanh 1
  • Feature parity with audio players like methods (pause, onComplete, onDuration / onCurrentPosition,  seek,...)

    Feature parity with audio players like methods (pause, onComplete, onDuration / onCurrentPosition, seek,...)

    Hi @frideosapps thank's for this wonderful library, this is exactly what I was looking for. My use case is that I would like to sync audio and text (mostly), save the progress of the user, etc ... I think I can achieve that with the callbacks (onStart => play song, onPause => pause song, ... )

    First, if I understand the docs correctly, I can only do play (startStages),rewind (resetStages), and skip next stage (getNextStage). It would be nice to have on the low level widget StagedObject a method like getPreviousStage and on an upper level have StagedWidget, have the corresponding utility methods and callbacks .

    Second, for the features like (pause, onComplete, onDuration / onCurrentPosition, seek) I guess it will have some repercussions on StreamedObject and ValueBuilder to have more control over the stream.

    What do you think ? I'm playing around your code right now to make a PR but for some reason I can't reset the player.

    Here is my raw code

    import 'package:frideos/frideos.dart';
    
    class HomePage extends StatefulWidget {
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      var reset = "reset";
      Stage stage;
      @override
      void initState() {
        staged
          ..setStagesMap(stagesMap)
          ..startStages();
        stage = staged.getStage(0);
        super.initState();
      }
    
      @override
      void dispose() {
        staged.dispose();
        super.dispose();
      }
    
      var staged = StagedObject();
      var stagesMap = <int, Stage>{
        0: Stage(
            widget: Container(
              key: const Key('0'),
              child: const Text('Stage 0'),
            ),
            time: 2000,
            onShow: () {
              //tester.pump();
            }),
        1: Stage(
            widget: Container(
              key: const Key('1'),
              child: const Text(
                'Stage 1',
                // style: TextStyle(fontSize: 50),
              ),
            ),
            time: 2000,
            onShow: () {}),
        2: Stage(
            widget: Container(
              key: const Key('2'),
              child: const Text(
                'Stage 2',
                // style: TextStyle(fontSize: 50),
              ),
            ),
            time: 2000,
            onShow: () {}),
      };
    
    // var stagesMap = ;
      @override
      Widget build(BuildContext context) {
        assert(stage != null);
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.refresh),
            onPressed: () {
             
                  staged
                //  ..setStagesMap(stagesMap)
                ..resetStages();
              setState(() {
                reset = "reset";
                staged = staged;
              });
              print(reset);
          
    
              // print(reset);
            },
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Center(
                child: ValueBuilder(
                    streamed: staged,
                    builder: (context, snapshot) => snapshot.data),
              ),
            ],
          ),
        );
      }
    }
    
    enhancement 
    opened by sachaarbonel 1
Owner
Francesco Mineo
Medical doctor and software developer. Flutter enthusiast.
Francesco Mineo
A light, powerful and reactive state management for Flutter Apps.

A light, powerful and reactive state management. Features ⚡️ Build for speed. ?? Reduce boilerplate code significantly. ?? Improve code readability. ?

2devs 3 Dec 15, 2022
Practice building basic animations in apps along with managing app state by BLoC State Management, Flutter Slider.

Practice building basic animations in apps along with managing app state by BLoC State Management including: Cubit & Animation Widget, Flutter Slider.

TAD 1 Jun 8, 2022
Arisprovider - A mixture between dependency injection (DI) and state management, built with widgets for widgets

A mixture between dependency injection (DI) and state management, built with wid

Behruz Hurramov 1 Jan 9, 2022
Greentick assignment - State management tool provider and UI components like ListView etc

greentick_assignment This project is a part of assignment, which demonstrate the

Vinod Patil 0 Feb 11, 2022
flutter app demos in devops, state management, UI, etc

Novice Flutter Doing this a bit different. Not the stuff in the Flutter Docs, but the missing stuff that no one is telling you about. Like: How to ext

Fred Grott 4 Apr 17, 2022
A Widget that passes a Reactive Model to all of it's children

scoped_model A set of utilities that allow you to easily pass a data Model from a parent Widget down to its descendants. In addition, it also rebuilds

Brian Egan 779 Nov 30, 2022
Nimbostratus is a reactive data-fetching and client-side cache management library built on top of Cloud Firestore.

Nimbostratus ?? Nimbostratus is a reactive data-fetching and client-side cache management library built on top of Cloud Firestore. The Cloud Firestore

Dan Reynolds 13 Dec 15, 2022
Flutter Control is complex library to maintain App and State management. Library merges multiple functionality under one hood. This approach helps to tidily bound separated logic into complex solution.

Flutter Control is complex library to maintain App and State management. Library merges multiple functionality under one hood. This approach helps to

Roman Hornak 23 Feb 23, 2022
GetX - one of the most popular state management solution in flutter

GteX Tutorial - Udemy GetX is one of the most popular state management solution in flutter. In addition to state management, GetX provides easy way to

Md. Siam 3 May 18, 2022
State Persistence - Persist state across app launches. By default this library store state as a local JSON file called `data.json` in the applications data directory. Maintainer: @slightfoot

State Persistence Persist state across app launches. By default this library store state as a local JSON file called data.json in the applications dat

Flutter Community 70 Sep 28, 2022
An extension to the bloc state management library which lets you create State Machine using a declarative API

An extension to the bloc state management library which lets you create State Machine using a declarative API

null 25 Nov 28, 2022
A powerful state machine for MobX management, that can be used in almost any application state.

A powerful state machine for MobX management, which can be used in almost any application state. It has 3 states - loading, success, error - and is pe

Daniel Magri 8 Oct 31, 2022
Flutter package to create list of radio button, by providing a list of objects it can be a String list or list of Map.

Custom Radio Group List Flutter package to create list of radio button, by providing a list of objects it can be a String list or list of Map. Feature

Ashok Kumar Verma 0 Nov 30, 2021
A package of pre-built `TextInputFormatter` objects

text_formatters A package of pre-built TextInputFormatter objects to use with Flutter's TextField or TextFormField widgets. Formatters UppercaseInputF

CodeGrue 4 Jul 24, 2020
Shopify Tag and Product Management App using Flutter and Riverpod State Management

Myshopify App A Simple Flutter Application project to get List of Tags, Products and Product Details from shopify https://shopicruit.myshopify.com/adm

Idowu Tomiwa 5 Nov 12, 2022
Create flutter project with all needed configuration in two minutes (theme, localization, connect to firebase, FCM, local notifications, safe API call, error handling, animation..etc)

Flutter GetX Template Flutter Getx template to make starting project fast and easy . Introduction We all face the same problem when we want to start a

Emad Beltaje 150 Jan 7, 2023
A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package. Built after react apollo

Flutter GraphQL Table of Contents Flutter GraphQL Table of Contents About this project Installation Usage GraphQL Provider [Graphql Link and Headers]

Snowball Digital 45 Nov 9, 2022
A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.

GraphQL Flutter ?? Bulletin See the v3 -> v4 Migration Guide if you're still on v3. Maintenance status: Low. Follow #762 for updates on the planned ar

Zino & Co. 3.1k Jan 5, 2023
A customizable carousel slider for Flutter. Supports infinite sliding, custom indicators, and custom animations with many pre-built indicators and animations.

Flutter Carousel Slider A customizable carousel slider for flutter Screenshots Installing dependencies: flutter_carousel_slider: ^1.0.8 Demo Demo a

Udara Wanasinghe 23 Nov 6, 2022