Skip to content

Commit

Permalink
Example Gallery (#26)
Browse files Browse the repository at this point in the history
misc: Improved the example folder with a gallery showcasing the different possibilities.
  • Loading branch information
brianegan authored and rrousselGit committed Jan 14, 2019
1 parent b264c90 commit 8d13fa4
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 71 deletions.
2 changes: 0 additions & 2 deletions example/.gitignore
Expand Up @@ -9,8 +9,6 @@
.buildlog/
.history
.svn/
/test
.metadata

# IntelliJ related
*.iml
Expand Down
10 changes: 10 additions & 0 deletions example/.metadata
@@ -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
12 changes: 12 additions & 0 deletions example/README.md
@@ -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!
47 changes: 47 additions & 0 deletions example/lib/custom_hook_function.dart
@@ -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;
}
107 changes: 44 additions & 63 deletions example/lib/main.dart
@@ -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;
}
92 changes: 92 additions & 0 deletions example/lib/use_effect.dart
@@ -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;
}
33 changes: 33 additions & 0 deletions example/lib/use_state.dart
@@ -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++,
),
);
}
}
44 changes: 44 additions & 0 deletions example/lib/use_stream.dart
@@ -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),
);
},
),
),
);
}
}

0 comments on commit 8d13fa4

Please sign in to comment.