refactor: use bloc pattern to manage state

This commit is contained in:
Rongjian Zhang 2019-01-23 16:50:51 +08:00
parent a051a54549
commit 0a1e65ec4f
8 changed files with 217 additions and 96 deletions

View File

@ -1,12 +1,9 @@
// import 'dart:async';
// import 'dart:convert';
import '../utils.dart';
import 'package:flutter/cupertino.dart';
// import 'package:graphql_flutter/graphql_flutter.dart';
// import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../components/event.dart';
// import 'user.dart';
import '../providers/event.dart';
import '../models/event.dart';
class HomeScreen extends StatefulWidget {
@ -17,33 +14,30 @@ class HomeScreen extends StatefulWidget {
}
class HomeScreenState extends State<HomeScreen> {
int page = 1;
List<Event> events = [];
// final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
// GlobalKey<RefreshIndicatorState>();
loadFirst() async {
events = await fetchEvents();
// print(events);
return events;
@override
void initState() {
super.initState();
// FIXME: context hack
// Future.delayed(Duration(seconds: 0)).then((_) {
// EventProvider.of(context).update.add(true);
// });
}
loadMore() async {
events.addAll(await fetchEvents(page + 1));
page++;
return events;
}
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
@override
Widget build(context) {
final eventBloc = EventProvider.of(context);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Home'),
),
child: FutureBuilder(
future: loadFirst(),
child: StreamBuilder<List<Event>>(
stream: eventBloc.events,
builder: (context, snapshot) {
// print(snapshot.data);
Widget widget;
if (snapshot.hasData) {
// List<Event> events = snapshot.data;
@ -59,7 +53,7 @@ class HomeScreenState extends State<HomeScreen> {
widget = ListView.builder(itemBuilder: (context, index) {
// print(index);
try {
return EventItem(events[index]);
return EventItem(snapshot.data[index]);
} catch (err) {
return Text(err.toString());
// return null;

View File

@ -1,7 +1,6 @@
import 'dart:async';
import '../utils.dart';
import 'package:flutter/cupertino.dart';
import '../models/notification.dart';
import '../providers/notification.dart';
class NotificationScreen extends StatefulWidget {
@override
@ -9,63 +8,74 @@ class NotificationScreen extends StatefulWidget {
}
class NotificationScreenState extends State<NotificationScreen> {
int tabIndex = 0;
bool loading = false;
List<NotificationItem> items = [];
initState() {
super.initState();
initFetch();
}
initFetch() async {
items = await fetchNotifications('all');
setState(() {});
// initFetch();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
print('dispose');
}
_onChange(int value) async {
setState(() {
loading = true;
tabIndex = value;
});
items = await fetchNotifications('all');
setState(() {
loading = false;
});
}
@override
Widget build(context) {
NotificationBloc bloc = NotificationProvider.of(context);
return SafeArea(
child: Column(
children: <Widget>[
CupertinoSegmentedControl(
groupValue: tabIndex,
onValueChanged: _onChange,
children: {
0: Text('Unread'),
1: Text('Paticipating'),
2: Text('All')
StreamBuilder<int>(
stream: bloc.active,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Text("loading...");
}
return CupertinoSegmentedControl(
groupValue: snapshot.data,
onValueChanged: (int index) {
bloc.activeUpdate.add(index);
},
children: {
0: Text('Unread'),
1: Text('Paticipating'),
2: Text('All')
},
);
},
),
Flexible(
child: loading
? CupertinoActivityIndicator()
: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => RichText(
text: TextSpan(
text: items[index].id,
style: TextStyle(color: CupertinoColors.black)),
)),
)
StreamBuilder<bool>(
stream: bloc.loading,
builder: (context, snapshot) {
return Flexible(
child: snapshot.data == null || snapshot.data
? CupertinoActivityIndicator()
: StreamBuilder<List<NotificationItem>>(
stream: bloc.items,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Text('loading...');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return RichText(
text: TextSpan(
text: snapshot
.data[index].repository.fullName,
style:
TextStyle(color: CupertinoColors.black),
),
);
},
);
},
),
);
}),
],
),
);

0
lib/ios/search.dart Normal file
View File

View File

@ -5,9 +5,15 @@ import 'package:flutter/cupertino.dart';
// import 'android/main.dart';
import 'ios/main.dart';
// import 'token.dart';
import 'providers/event.dart';
import 'providers/notification.dart';
class App extends StatelessWidget {
final isIos = true;
final EventBloc eventBloc;
final NotificationBloc notificationBloc;
App(this.eventBloc, this.notificationBloc);
// final ValueNotifier<GraphQLClient> client = ValueNotifier(
// GraphQLClient(
@ -21,21 +27,32 @@ class App extends StatelessWidget {
@override
build(context) {
return MaterialApp(
title: 'GitFlux',
theme: ThemeData(
primarySwatch: Colors.blue,
return NotificationProvider(
bloc: notificationBloc,
child: EventProvider(
bloc: eventBloc,
child: MaterialApp(
title: 'GitFlux',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DefaultTextStyle(
style: TextStyle(color: CupertinoColors.black),
child: IosHomePage(title: 'GitFlux'),
),
routes: {
// '/notification': (context) => IosNotificationTab(),
// '/profile': (context) => IosProfileTab(),
},
),
),
home: DefaultTextStyle(
style: TextStyle(color: CupertinoColors.black),
child: IosHomePage(title: 'GitFlux'),
),
routes: {
// '/notification': (context) => IosNotificationTab(),
// '/profile': (context) => IosProfileTab(),
},
);
}
}
void main() => runApp(App());
void main() {
EventBloc eventBloc = EventBloc();
NotificationBloc notificationBloc = NotificationBloc();
runApp(App(eventBloc, notificationBloc));
}

60
lib/providers/event.dart Normal file
View File

@ -0,0 +1,60 @@
import 'package:flutter/widgets.dart';
import 'dart:async';
import 'package:rxdart/subjects.dart';
import '../models/event.dart';
import 'package:rxdart/rxdart.dart';
import '../utils.dart';
Future<List<Event>> fetchEvents([int page = 1]) async {
List data = await getWithCredentials(
'/users/pd4d10/received_events/public?page=$page',
);
return data.map<Event>((item) {
return Event.fromJson(item);
}).toList();
}
class EventBloc {
final _items = BehaviorSubject<List<Event>>(seedValue: []);
final _page = BehaviorSubject(seedValue: 0);
final _update = StreamController<bool>();
// Stream<int> get eventPage => _page.stream;
Stream<List<Event>> get events => _items.stream;
Sink<bool> get update => _update.sink;
EventBloc() {
_update.stream.listen((bool isRefresh) async {
if (isRefresh) {
_items.add(await fetchEvents());
_page.add(1);
} else {
_items.add(await fetchEvents());
_page.add(_page.value + 1);
}
});
}
void dispose() {
_items.close();
_page.close();
}
}
class EventProvider extends InheritedWidget {
final EventBloc bloc;
EventProvider({
Key key,
Widget child,
@required EventBloc bloc,
}) : bloc = bloc,
super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static EventBloc of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(EventProvider) as EventProvider)
.bloc;
}

View File

@ -0,0 +1,58 @@
import 'package:flutter/widgets.dart';
import 'package:rxdart/subjects.dart';
import 'package:rxdart/rxdart.dart';
import '../utils.dart';
import '../models/notification.dart';
Future<List<NotificationItem>> fetchNotifications([int index = 0]) async {
String search = '';
if (index == 1) {
search = '?paticipating=true';
} else if (index == 2) {
search = '?all=true';
}
List data = await getWithCredentials('/notifications$search');
// print(data.length);
return data
.map<NotificationItem>((item) => NotificationItem.fromJson(item))
.toList();
}
class NotificationBloc {
final _items = BehaviorSubject<List<NotificationItem>>(seedValue: []);
final _active = BehaviorSubject(seedValue: 0);
final _loading = BehaviorSubject(seedValue: false);
Stream<List<NotificationItem>> get items => _items.stream;
Stream<int> get active => _active.stream;
Stream<bool> get loading => _loading.stream;
Sink<int> get activeUpdate => _active.sink;
NotificationBloc() {
_active.stream.listen((int index) async {
_loading.add(true);
_items.add(await fetchNotifications(index));
_loading.add(false);
});
}
}
class NotificationProvider extends InheritedWidget {
final NotificationBloc bloc;
NotificationProvider({
Key key,
Widget child,
@required NotificationBloc bloc,
}) : bloc = bloc,
super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static NotificationBloc of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(NotificationProvider)
as NotificationProvider)
.bloc;
}

View File

@ -3,9 +3,7 @@ import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'token.dart';
import 'models/event.dart';
import 'models/user.dart';
import 'models/notification.dart';
final prefix = 'https://api.github.com';
@ -21,24 +19,7 @@ Future<dynamic> getWithCredentials(String url) async {
return data;
}
Future<List<Event>> fetchEvents([int page = 1]) async {
List data = await getWithCredentials(
'/users/pd4d10/received_events/public?page=$page',
);
return data.map<Event>((item) {
return Event.fromJson(item);
}).toList();
}
Future<User> fetchUser(String login) async {
Map<String, dynamic> data = await getWithCredentials('/users/$login');
return User.fromJson(data);
}
Future<List<NotificationItem>> fetchNotifications(String type) async {
List data = await getWithCredentials('/notifications?$type=true');
print(data);
return data
.map<NotificationItem>((item) => NotificationItem.fromJson(item))
.toList();
}

View File

@ -21,6 +21,7 @@ dependencies:
cupertino_icons: ^0.1.2
http: ^0.11.3
graphql_flutter: ^1.0.0-alpha
rxdart: ^0.20.0
dev_dependencies:
flutter_test: