Skip to content

Commit

Permalink
docs: add infinite scroll example
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoryConrad committed Jul 29, 2023
1 parent 1b187ac commit 7c29af9
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 0 deletions.
97 changes: 97 additions & 0 deletions packages/examples/infinite_scroll/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:flutter_rearch/flutter_rearch.dart';
import 'package:rearch/rearch.dart';

void main() => runApp(const MyApp());

/// A factory capsule that returns the input data after a brief delay.
/// This also doubles as an example of a generic capsule.
Future<T> Function(T) delayedEchoFactory<T>(CapsuleHandle _) {
return (data) => Future.delayed(const Duration(seconds: 1), () => data);
}

/// {@template MyApp}
/// The root of the infinite scroll demo.
/// {@endtemplate}
class MyApp extends StatelessWidget {
/// {@macro MyApp}
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return RearchBootstrapper(
child: MaterialApp(
title: 'Infinite Scroll Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Infinite Scroll Demo')),
body: const Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Text(
'Selected items will not be disposed when they are '
'scrolled off screen (they are using keep alives)',
),
),
Expanded(child: InfiniteList()),
],
),
),
),
);
}
}

/// {@template InfiniteList}
/// The infinite list of numbers in the application.
/// {@endtemplate}
class InfiniteList extends StatelessWidget {
/// {@macro InfiniteList}
const InfiniteList({super.key});

@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) => InfiniteScrollItem(index: index),
);
}
}

/// {@template InfiniteScrollItem}
/// An item in the infinitely scrolling [ListView].
/// {@endtemplate}
class InfiniteScrollItem extends RearchConsumer {
/// {@macro InfiniteScrollItem}
const InfiniteScrollItem({required this.index, super.key});

/// The index of this item.
final int index;

@override
Widget build(BuildContext context, WidgetHandle use) {
final (keepAlive, setKeepAlive) = use.state(false);
use.automaticKeepAlive(keepAlive: keepAlive);

final factory = use(delayedEchoFactory<int>);
final echoFuture = use.memo(() => factory(index), [factory, index]);
final echoState = use.future(echoFuture);

return ListTile(
selected: keepAlive,
onTap: () => setKeepAlive(!keepAlive),
leading: Icon(
keepAlive
? Icons.task_alt_rounded
: Icons.radio_button_unchecked_rounded,
),
title: switch (echoState) {
AsyncData(:final data) => Text('$data'),
AsyncLoading() => const Align(
alignment: Alignment.centerLeft,
child: CircularProgressIndicator.adaptive(),
),
AsyncError(:final error) => Text('$error'),
},
);
}
}
19 changes: 19 additions & 0 deletions packages/examples/infinite_scroll/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: infinite_scroll
description: Rearch example application showcasing infinite scroll.
publish_to: 'none'

environment:
sdk: '>=3.0.0 <4.0.0'

dependencies:
flutter:
sdk: flutter
flutter_rearch: ^0.0.0-dev.7
rearch: ^0.0.0-dev.7

dev_dependencies:
flutter_test:
sdk: flutter

flutter:
uses-material-design: true
88 changes: 88 additions & 0 deletions packages/examples/infinite_scroll/test/scroll_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_rearch/flutter_rearch.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:infinite_scroll/main.dart';

const itemHeight = 48.0;
const numItemsToShow = 5;
const scrollOffset = Offset(0, 3 * numItemsToShow * itemHeight);

void main() {
testWidgets('keep alive items are kept alive when scrolled', (tester) async {
Future<void> scrollUp() async {
await tester.drag(find.byType(InfiniteList), scrollOffset);
await tester.pump();
}

Future<void> scrollDown() async {
await tester.drag(find.byType(InfiniteList), -scrollOffset);
await tester.pump();
}

void expectItems(Iterable<int> iterable, Matcher matcher) {
for (final i in iterable) {
expect(find.text('$i'), matcher);
}
}

await tester.pumpWidget(
const RearchBootstrapper(
child: MaterialApp(
home: Scaffold(
body: SizedBox(
height: numItemsToShow * itemHeight,
child: InfiniteList(),
),
),
),
),
);

expectItems(Iterable<int>.generate(numItemsToShow * 2), findsNothing);

await tester.pumpAndSettle();
expectItems(Iterable<int>.generate(numItemsToShow), findsOneWidget);
expectItems(
Iterable<int>.generate(numItemsToShow, (i) => i + numItemsToShow),
findsNothing,
);

await tester.tap(find.text('0'));
await tester.tap(find.text('2'));
await tester.pumpAndSettle();

await scrollDown();
await tester.pumpAndSettle();
expectItems(Iterable<int>.generate(numItemsToShow), findsNothing);

await scrollUp();
expectItems([0, 2], findsOneWidget);
expectItems([1, 3, 4, 5], findsNothing);

await tester.pumpAndSettle();
expectItems(Iterable<int>.generate(numItemsToShow), findsOneWidget);
expectItems(
Iterable<int>.generate(numItemsToShow, (i) => i + numItemsToShow),
findsNothing,
);

await tester.tap(find.text('0'));
await tester.tap(find.text('2'));
await tester.pump();

await scrollDown();
await tester.pumpAndSettle();
expectItems(Iterable<int>.generate(numItemsToShow), findsNothing);

await scrollUp();
expectItems(Iterable<int>.generate(numItemsToShow * 2), findsNothing);

await tester.pumpAndSettle();
expectItems(Iterable<int>.generate(numItemsToShow), findsOneWidget);
expectItems(
Iterable<int>.generate(numItemsToShow, (i) => i + numItemsToShow),
findsNothing,
);
});
}
Binary file added packages/examples/infinite_scroll/web/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions packages/examples/infinite_scroll/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">

<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">

<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="infinite_scroll">
<link rel="apple-touch-icon" href="icons/Icon-192.png">

<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>

<title>infinite_scroll</title>
<link rel="manifest" href="manifest.json">

<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>
35 changes: 35 additions & 0 deletions packages/examples/infinite_scroll/web/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "infinite_scroll",
"short_name": "infinite_scroll",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

0 comments on commit 7c29af9

Please sign in to comment.