Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
misc: Improved the example folder with a gallery showcasing the different possibilities.
- Loading branch information
1 parent
b264c90
commit 8d13fa4
Showing
9 changed files
with
285 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,6 @@ | |
.buildlog/ | ||
.history | ||
.svn/ | ||
/test | ||
.metadata | ||
|
||
# IntelliJ related | ||
*.iml | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# This file tracks properties of this Flutter project. | ||
# Used by Flutter tool to assess capabilities and perform upgrades etc. | ||
# | ||
# This file should be version controlled and should not be manually edited. | ||
|
||
version: | ||
revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b | ||
channel: stable | ||
|
||
project_type: app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Flutter Hooks Gallery | ||
|
||
A series of examples demonstrating how to use Flutter Hooks! It teaches how to | ||
use the Widgets and hooks that are provided by this library, as well examples | ||
demonstrating how to write custom hooks. | ||
|
||
## Run the app | ||
|
||
1. Open a terminal | ||
2. Navigate to this `example` directory | ||
3. Run `flutter create .` | ||
4. Run `flutter run` from your Terminal, or launch the project from your IDE! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// ignore_for_file: omit_local_variable_types | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
|
||
/// This example demonstrates how to write a hook function that enhances the | ||
/// useState hook with logging functionality. | ||
class CustomHookFunctionExample extends HookWidget { | ||
@override | ||
Widget build(BuildContext context) { | ||
// Next, invoke the custom `useLoggedState` hook with a default value to | ||
// create a `counter` variable that contains a `value`. Whenever the value | ||
// is changed, this Widget will be rebuilt and the result will be logged! | ||
final counter = useLoggedState(0); | ||
|
||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Custom Hook: Function'), | ||
), | ||
body: Center( | ||
// Read the current value from the counter | ||
child: Text('Button tapped ${counter.value} times'), | ||
), | ||
floatingActionButton: FloatingActionButton( | ||
child: const Icon(Icons.add), | ||
// When the button is pressed, update the value of the counter! This | ||
// will trigger a rebuild as well as printing the latest value to the | ||
// console! | ||
onPressed: () => counter.value++, | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// A custom hook that wraps the useState hook to add logging. Hooks can be | ||
/// composed -- meaning you can use hooks within hooks! | ||
ValueNotifier<T> useLoggedState<T>([T initialData]) { | ||
// First, call the useState hook. It will create a ValueNotifier for you that | ||
// rebuilds the Widget whenever the value changes. | ||
final result = useState<T>(initialData); | ||
|
||
// Next, call the useValueChanged hook to print the state whenever it changes | ||
useValueChanged(result.value, (T _, T __) { | ||
print(result.value); | ||
}); | ||
|
||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,60 @@ | ||
// ignore_for_file: omit_local_variable_types | ||
import 'dart:async'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
import 'package:shared_preferences/shared_preferences.dart'; | ||
import 'package:flutter_hooks_gallery/use_effect.dart'; | ||
import 'package:flutter_hooks_gallery/use_state.dart'; | ||
import 'package:flutter_hooks_gallery/use_stream.dart'; | ||
|
||
void main() => runApp(_MyApp()); | ||
void main() => runApp(HooksGalleryApp()); | ||
|
||
class _MyApp extends StatelessWidget { | ||
/// An App that demonstrates how to use hooks. It includes examples that cover | ||
/// the hooks provided by this library as well as examples that demonstrate | ||
/// how to write custom hooks. | ||
class HooksGalleryApp extends StatelessWidget { | ||
@override | ||
Widget build(BuildContext context) { | ||
return const MaterialApp( | ||
title: 'Flutter Demo', | ||
home: _Counter(), | ||
return MaterialApp( | ||
title: 'Flutter Hooks Gallery', | ||
home: Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Flutter Hooks Gallery'), | ||
), | ||
body: ListView(children: [ | ||
_GalleryItem( | ||
title: 'useState', | ||
builder: (context) => UseStateExample(), | ||
), | ||
_GalleryItem( | ||
title: 'useMemoize + useStream', | ||
builder: (context) => UseStreamExample(), | ||
), | ||
_GalleryItem( | ||
title: 'Custom Hook Function', | ||
builder: (context) => CustomHookExample(), | ||
), | ||
]), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class _Counter extends HookWidget { | ||
const _Counter({Key key}) : super(key: key); | ||
class _GalleryItem extends StatelessWidget { | ||
final String title; | ||
final WidgetBuilder builder; | ||
|
||
const _GalleryItem({this.title, this.builder}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
StreamController<int> countController = | ||
_useLocalStorageInt(context, 'counter'); | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Counter app'), | ||
), | ||
body: Center( | ||
child: HookBuilder( | ||
builder: (context) { | ||
AsyncSnapshot<int> count = useStream(countController.stream); | ||
|
||
return !count.hasData | ||
// Currently loading value from local storage, or there's an error | ||
? const CircularProgressIndicator() | ||
: GestureDetector( | ||
onTap: () => countController.add(count.data + 1), | ||
child: Text('You tapped me ${count.data} times.'), | ||
); | ||
}, | ||
), | ||
), | ||
return ListTile( | ||
title: Text(title), | ||
onTap: () { | ||
Navigator.push( | ||
context, | ||
MaterialPageRoute<void>( | ||
builder: builder, | ||
), | ||
); | ||
}, | ||
); | ||
} | ||
} | ||
|
||
StreamController<int> _useLocalStorageInt( | ||
BuildContext context, | ||
String key, { | ||
int defaultValue = 0, | ||
}) { | ||
final controller = useStreamController<int>(keys: <dynamic>[key]); | ||
|
||
// We define a callback that will be called on first build | ||
// and whenever the controller/key change | ||
useEffect(() { | ||
// We listen to the data and push new values to local storage | ||
final sub = controller.stream.listen((data) async { | ||
final prefs = await SharedPreferences.getInstance(); | ||
await prefs.setInt(key, data); | ||
}); | ||
// Unsubscribe when the widget is disposed | ||
// or on controller/key change | ||
return sub.cancel; | ||
}, <dynamic>[controller, key]); | ||
// We load the initial value | ||
useEffect(() { | ||
SharedPreferences.getInstance().then((prefs) async { | ||
int valueFromStorage = prefs.getInt(key); | ||
controller.add(valueFromStorage ?? defaultValue); | ||
}).catchError(controller.addError); | ||
// ensure the callback is called only on first build | ||
}, <dynamic>[controller, key]); | ||
|
||
return controller; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// ignore_for_file: omit_local_variable_types | ||
import 'dart:async'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
import 'package:shared_preferences/shared_preferences.dart'; | ||
|
||
/// This example demonstrates how to create a custom Hook. | ||
class CustomHookExample extends HookWidget { | ||
@override | ||
Widget build(BuildContext context) { | ||
// Consume the custom hook. It returns a StreamController that we can use | ||
// within this Widget. | ||
// | ||
// To update the stored value, `add` data to the StreamController. To get | ||
// the latest value from the StreamController, listen to the Stream with | ||
// the useStream hook. | ||
StreamController<int> countController = _useLocalStorageInt('counter'); | ||
|
||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Custom Hook example'), | ||
), | ||
body: Center( | ||
// Use a HookBuilder Widget to listen to the Stream. This ensures a | ||
// smaller portion of the Widget tree is rebuilt when the stream emits a | ||
// new value | ||
child: HookBuilder( | ||
builder: (context) { | ||
AsyncSnapshot<int> count = useStream(countController.stream); | ||
|
||
return !count.hasData | ||
? const CircularProgressIndicator() | ||
: GestureDetector( | ||
onTap: () => countController.add(count.data + 1), | ||
child: Text('You tapped me ${count.data} times.'), | ||
); | ||
}, | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
// A custom hook that will read and write values to local storage using the | ||
// SharedPreferences package. | ||
StreamController<int> _useLocalStorageInt( | ||
String key, { | ||
int defaultValue = 0, | ||
}) { | ||
// Custom hooks can use additional hooks internally! | ||
final controller = useStreamController<int>(keys: <dynamic>[key]); | ||
|
||
// Pass a callback to the useEffect hook. This function should be called on | ||
// first build and every time the controller or key changes | ||
useEffect( | ||
() { | ||
// Listen to the StreamController, and when a value is added, store it | ||
// using SharedPrefs. | ||
final sub = controller.stream.listen((data) async { | ||
final prefs = await SharedPreferences.getInstance(); | ||
await prefs.setInt(key, data); | ||
}); | ||
// Unsubscribe when the widget is disposed | ||
// or on controller/key change | ||
return sub.cancel; | ||
}, | ||
// Pass the controller and key to the useEffect hook. This will ensure the | ||
// useEffect hook is only called the first build or when one of the the | ||
// values changes. | ||
<dynamic>[controller, key], | ||
); | ||
|
||
// Load the initial value from local storage and add it as the initial value | ||
// to the controller | ||
useEffect( | ||
() { | ||
SharedPreferences.getInstance().then((prefs) async { | ||
int valueFromStorage = prefs.getInt(key); | ||
controller.add(valueFromStorage ?? defaultValue); | ||
}).catchError(controller.addError); | ||
}, | ||
// Pass the controller and key to the useEffect hook. This will ensure the | ||
// useEffect hook is only called the first build or when one of the the | ||
// values changes. | ||
<dynamic>[controller, key], | ||
); | ||
|
||
// Finally, return the StreamController. This allows users to add values from | ||
// the Widget layer and listen to the stream for changes. | ||
return controller; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
|
||
/// This example emulates the basic Counter app generated by the | ||
/// `flutter create` command to demonstrates the `useState` hook. | ||
/// | ||
/// First, instead of a StatefulWidget, use a HookWidget instead! | ||
class UseStateExample extends HookWidget { | ||
@override | ||
Widget build(BuildContext context) { | ||
// Next, invoke the `useState` function with a default value to create a | ||
// `counter` variable that contains a `value`. Whenever the value is | ||
// changed, this Widget will be rebuilt! | ||
final counter = useState(0); | ||
|
||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('useState example'), | ||
), | ||
body: Center( | ||
// Read the current value from the counter | ||
child: Text('Button tapped ${counter.value} times'), | ||
), | ||
floatingActionButton: FloatingActionButton( | ||
child: const Icon(Icons.add), | ||
// When the button is pressed, update the value of the counter! This | ||
// will trigger a rebuild, displaying the latest value in the Text | ||
// Widget above! | ||
onPressed: () => counter.value++, | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
|
||
/// This example demonstrates how to use Hooks to rebuild a Widget whenever | ||
/// a Stream emits a new value. | ||
class UseStreamExample extends StatelessWidget { | ||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('useStream example'), | ||
), | ||
body: Center( | ||
// In this example, the Text Widget is the only portion that needs to | ||
// rebuild when the Stream changes. Therefore, use a HookBuilder | ||
// Widget to limit rebuilds to this section of the app, rather than | ||
// marking the entire UseStreamExample as a HookWidget! | ||
child: HookBuilder( | ||
builder: (context) { | ||
// First, create and cache a Stream with the `useMemoized` hook. | ||
// This hook allows you to create an Object (such as a Stream or | ||
// Future) the first time this builder function is invoked without | ||
// recreating it on each subsequent build! | ||
final stream = useMemoized( | ||
() => Stream<int>.periodic(Duration(seconds: 1), (i) => i + 1), | ||
); | ||
// Next, invoke the `useStream` hook to listen for updates to the | ||
// Stream. This triggers a rebuild whenever a new value is emitted. | ||
// | ||
// Like normal StreamBuilders, it returns the current AsyncSnapshot. | ||
final snapshot = useStream(stream); | ||
|
||
// Finally, use the data from the Stream to render a text Widget. | ||
// If no data is available, fallback to a default value. | ||
return Text( | ||
'${snapshot.data ?? 0}', | ||
style: const TextStyle(fontSize: 36), | ||
); | ||
}, | ||
), | ||
), | ||
); | ||
} | ||
} |
Oops, something went wrong.