stateful_props 0.2.0 copy "stateful_props: ^0.2.0" to clipboard
stateful_props: ^0.2.0 copied to clipboard

Provides a simpler way to create stateful components in flutter. Similar in concept to "hooks" but with a more object oriented flavor.

example/lib/main.dart

import 'package:example/optimized_rebuilds.dart';
import 'package:example/test_widgets/core_test_widgets.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
import 'package:stateful_props/stateful_props.dart';

import 'basic_animator.dart';
import 'basic_builders.dart';
import 'basic_focus.dart';
import 'basic_keyboard.dart';
import 'basic_text_controller.dart';
import 'scroll_to_top_example.dart';
import 'sync_props_example.dart';

export 'comparison_stack.dart';

void main() {
  runApp(ListenableProvider<DurationNotifier>.value(
      value: defaultDuration,
      child: MaterialApp(
        home: Scaffold(
          //body: StatefulPropsDemo(),
          //body: SmallKitchenSink(),
          body: SmallKitchenSink(),
        ),
      )));
}

class DurationNotifier extends ValueNotifier<double> {
  DurationNotifier(double value) : super(value);
}

final defaultDuration = DurationNotifier(1);

class SimpleProviderTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // TextButton(
        //   onPressed: () => defaultDuration.value = Random().nextDouble() * 3,
        //   child: Text('change'),
        // ),
        SmallKitchenSink(),
        //SmallKitchenSinkWithMixin(),
      ],
    );
  }
}

class SmallKitchenSinkWithMixin extends StatefulWidget {
  @override
  State<SmallKitchenSinkWithMixin> createState() => _SmallKitchenSinkWithMixinState();
}

class _SmallKitchenSinkWithMixinState extends State<SmallKitchenSinkWithMixin> with StatefulPropsMixin {
  late final _layout = useProp(LayoutProp());
  late final _tapCount = useProp(IntProp());
  late final _anim = useProp(AnimationControllerProp(1, autoBuild: false));
  late final _text = useProp(TextEditingControllerProp(text: 'hello', onChanged: _handleTextChanged));

  @override
  void initProps() => useProp(TapDetectorProp(_handleTap));

  void _handleTap() {
    _tapCount.value++;
    _anim.controller.forward(from: 0.0);
  }

  void _handleTextChanged(TextEditingControllerProp prop) => print(prop.text);

  @override
  Widget buildWithProps(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          FadeTransition(opacity: _anim.controller, child: Text('FADE ME')),
          Text('${_tapCount.value}'),
          Text('${_layout.constraints.maxWidth}'),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(controller: _text.controller),
          )
        ],
      ),
    );
  }
}

class SmallKitchenSink extends StatefulPropsWidget<SmallKitchenSink> {
  late final _layout = useProp('layout', () => LayoutProp());
  late final _tapCount = useProp('count', () => IntProp());
  late final _anim = useProp('anim', () => AnimationControllerProp(1, autoBuild: true));
  late final _text = useProp('text', () {
    return TextEditingControllerProp(text: 'hello', onChanged: _handleTextChanged);
  });

  void _handleTap() {
    _tapCount.value++;
    _anim.controller.forward(from: 0);
  }

  void _handleTextChanged(TextEditingControllerProp prop) => print(prop.text);

  @override
  Widget buildWithProps(BuildContext context) {
    useProp('tap', () => TapDetectorProp(_handleTap));
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Opacity(opacity: _anim.controller.value, child: Text('FADE ME')),
          Text('${_tapCount.value}'),
          Text('${_layout.constraints.maxWidth}'),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(controller: _text.controller),
          )
        ],
      ),
    );
  }
}

class _SimpleProviderTestView extends StatefulWidget {
  @override
  State<_SimpleProviderTestView> createState() => _SimpleProviderTestViewState();
}

class _SimpleProviderTestViewState extends State<_SimpleProviderTestView> with StatefulPropsMixin {
  late StringProp someString = useDependentProp((c, __) => StringProp(initial: c.watch<ValueNotifier<String>>().value));

  @override
  Widget buildWithProps(BuildContext context) {
    return Center(child: Text('${someString.value}'));
  }
}

// Create a list of experiments/tests so we can make a tab-menu from them.
class Experiment {
  final String title;
  final Widget Function() builder;
  Experiment(this.title, this.builder);
}

List<Experiment> widgets = [
  Experiment("OptimizedBuilders", () => OptimizedRebuildsExample()),
  Experiment("Builders", () => BasicBuilderExample()),
  Experiment("Animator", () => BasicAnimatorExample()),
  Experiment("TextEdit", () => BasicTextControllerExample()),
  //TODO: DependencySync needs better controls for changing provided data
  Experiment("DependencySync", () => SyncExample()),
  Experiment("KeyboardListener", () => BasicKeyboardExample()),
  Experiment("FocusNode", () => BasicFocusExample()),
  Experiment("ScrollAndFadeIn", () => ScrollToTopExample()),
];

// Demo wraps a bunch of tests
class StatefulPropsDemo extends StatefulWidget {
  @override
  _StatefulPropsDemoState createState() => _StatefulPropsDemoState();
}

class _StatefulPropsDemoState extends State<StatefulPropsDemo> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    // Create a list of btns for each experiment
    List<Widget> bottomBtns = List.generate(widgets.length, (index) => Expanded(child: _buildBtn(index)));
    // Provide a ChangeNotifier any of the Examples can use
    return ChangeNotifierProvider(
      create: (_) => Deps(),
      // Provide root restoration scope in case some of the demos want to test it
      child: RootRestorationScope(
        restorationId: "statefulDemo",
        child: Row(
          children: [
            /// ///////////////////////////
            /// Left Menu (Provider Config)
            ProviderMenu(),
            Expanded(
              child: Column(children: [
                /// ///////////////////////////
                /// Main Content
                Expanded(
                  child: AnimatedSwitcher(
                    duration: Duration(milliseconds: 300),
                    child: widgets[_index].builder.call(),
                  ),
                ),

                /// ///////////////////////////
                /// Bottom Menu
                Row(mainAxisAlignment: MainAxisAlignment.center, children: bottomBtns)
              ]),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildBtn(int index) {
    return FlatButton(
      padding: EdgeInsets.symmetric(vertical: 40),
      onPressed: () => setState(() => _index = index),
      child: Text(
        widgets[index].title,
        style: TextStyle(fontWeight: _index == index ? FontWeight.bold : FontWeight.normal),
      ),
    );
  }
}

class ProviderMenu extends StatefulWidget {
  @override
  _ProviderMenuState createState() => _ProviderMenuState();
}

class _ProviderMenuState extends State<ProviderMenu> with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    Deps deps = Provider.of(context);
    return Container(
      padding: EdgeInsets.all(24),
      color: Colors.grey.shade300,
      child: Column(
        children: [
          Text("Provided Values", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          OutlineButton(
            child: Text("Toggle: ${deps.toggle}"),
            onPressed: () => deps.toggle = !deps.toggle,
          ),
          OutlineButton(
            child: Text("Duration: ${deps.seconds}"),
            onPressed: () {
              deps.seconds++;
              if (deps.seconds > 3) deps.seconds = .5;
            },
          ),
          OutlineButton(
            child: Text("Inject Vsync: ${deps.vsync == null ? "false" : "true"}"),
            onPressed: () {
              deps.vsync = deps.vsync == null ? this : null;
            },
          ),
        ],
      ),
    );
  }
}

class Deps extends ChangeNotifier {
  double _duration = 1;
  bool _toggle = false;

  bool get toggle => _toggle;
  set toggle(bool toggle) {
    _toggle = toggle;
    notifyListeners();
  }

  double get seconds => _duration;
  set seconds(double duration) {
    _duration = duration;
    notifyListeners();
  }

  TickerProvider? _vsync;
  TickerProvider? get vsync => _vsync;
  set vsync(TickerProvider? vsync) {
    _vsync = vsync;
    notifyListeners();
  }
}

Deps deps = Deps();
21
likes
140
points
42
downloads

Publisher

verified publishergskinner.com

Weekly Downloads

Provides a simpler way to create stateful components in flutter. Similar in concept to "hooks" but with a more object oriented flavor.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on stateful_props