() => Text("FC in Flutter")

Pub Version Github Action

An easy way to create Functional Components (FC) in Flutter, with composable hooks.

The FC has been deployed in some production app builds.

Features

  • ⏱️ Never wait for code generation
  • 🖨️ Never verbosing State***Widget classes
  • 📄 Tiny implementations without external deps
  • 🪝 With powerful composable hooks
  • 🐇 Speed up developing
  • 🧱 Hot reload
  • ⚛️ React style friendly

Install

dependencies:
  flutter_fc: <latest version>

Quick Example

FCs may be come out as FCWidget classes, or by defineFC

By extending FCWidget

class Counter extends FCWidget {
  const Counter({super.key});

  @override
  Widget build(BuildContext context) {
    final (counter, setCounter) = useState(0);
    return ElevatedButton(
      onPressed: () => setCounter(counter + 1),
        child: Text("Counter: $counter"),
    );
  }
}

By plain function

final Counter = defineFC((props) {
  final (counter, setCounter) = useState(0);
  return ElevatedButton(
    onPressed: () => setCounter(counter + 1),
      child: Text("Counter: $counter"),
  );
});

Equip Powerful Hooks

Currently supports these hooks as following:

useState

// Dart >= 3
final (flag, setFlag) = useState(false);

// Dart >= 2.12 < 3.0.0
final state = useState(false);
final flag = state.$1, setFlag = state.$2;

useEffect

late Steam stream;
useEffect(() {
  final sub = stream.listen((e) {...});
  return () => sub.cancel();
}, [stream]);

useMemo

final time = 0;
final seconds = useMemo(() => "${time}s");

useRef

final ref = useRef(0);
ref.current; // 0

ref.current = 1;
ref.current; // 1

useImperativeHandle

FCRef<Function()?> reloadRef;

useImperativeHandle(reloadRef, () {
  return () => reloadSomething();
});

// parent
reloadRef.current?.call();

Development Tips

Define Reusable Widgets

class Counter extends FCWidget {
  final int? value;
  Counter({this.value, super.key});

  @override
  Widget build(BuildContext context) {
    final (counter, setCounter) = useState(value ?? 0);
    useEffect(() => setCounter(value ?? 0), [value]);
    return Text("Counter: $counter"");
  }
}

Hot Reload

Dynamic closures are not reassembled during hot reload.To apply hot reload, move the function out of scope.

// [NO] Define from closure.
final Counter = defineFC((props) {
  final (counter, setCounter) = useState(0);
  return ElevatedButton(
    onPressed: () => setCounter(counter + 1),
      child: Text("Counter: $counter"),
  );
});

// [OK] Define from const function
_Counter(props) {
  final (counter, setCounter) = useState(0);
  return ElevatedButton(
    onPressed: () => setCounter(counter + 1),
      child: Text("Counter: $counter"),
  );
}
final Counter = defineFC(_Counter);

Ignore Naming Warnings

To avoid IDE lint warnings, include FC lints preset.

# analysis_options.yaml
include: package:flutter_fc/lints.yaml

or configure manually.

analyzer:
  errors:
    body_might_complete_normally_nullable: ignore

linter:
  rules:
    non_constant_identifier_names: false

Acknowledgement

React

Dart 3

License

MIT (c) 2023-present, Luo3House.

Libraries

flutter_fc