Skip to content

Commit ed3f0ec

Browse files
committed
调整redux
1 parent 4de42cf commit ed3f0ec

File tree

13 files changed

+374
-83
lines changed

13 files changed

+374
-83
lines changed

lib/common/net/api.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:dio/dio.dart';
2+
import 'package:gsy_github_app_flutter/common/net/code.dart';
23

34
import 'dart:collection';
45

@@ -8,6 +9,7 @@ import 'package:gsy_github_app_flutter/common/net/interceptors/log_interceptor.d
89

910
import 'package:gsy_github_app_flutter/common/net/interceptors/response_interceptor.dart';
1011
import 'package:gsy_github_app_flutter/common/net/interceptors/token_interceptor.dart';
12+
import 'package:gsy_github_app_flutter/common/net/result_data.dart';
1113

1214
///http请求
1315
class HttpManager {
@@ -19,7 +21,6 @@ class HttpManager {
1921
final TokenInterceptors _tokenInterceptors = new TokenInterceptors();
2022

2123
HttpManager() {
22-
2324
_dio.interceptors.add(new HeaderInterceptors());
2425

2526
_dio.interceptors.add(_tokenInterceptors);
@@ -49,13 +50,20 @@ class HttpManager {
4950
option.headers = headers;
5051
}
5152

52-
option.headers[NOT_TIP_KEY] = noTip;
53-
5453
Response response;
5554
try {
5655
response = await _dio.request(url, data: params, options: option);
57-
} catch (e) {
58-
print(e.toString());
56+
} on DioError catch (e) {
57+
Response errorResponse;
58+
if (e.response != null) {
59+
errorResponse = e.response;
60+
} else {
61+
errorResponse = new Response(statusCode: 666);
62+
}
63+
if (e.type == DioErrorType.CONNECT_TIMEOUT || e.type == DioErrorType.RECEIVE_TIMEOUT) {
64+
errorResponse.statusCode = Code.NETWORK_TIMEOUT;
65+
}
66+
return new ResultData(Code.errorHandleFunction(errorResponse.statusCode, e.message, noTip), false, errorResponse.statusCode);
5967
}
6068
return response.data;
6169
}

lib/common/net/interceptors/error_interceptor.dart

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,9 @@ class ErrorInterceptors extends InterceptorsWrapper {
2020
onRequest(RequestOptions options) async {
2121
//没有网络
2222
var connectivityResult = await (new Connectivity().checkConnectivity());
23-
var noTip = options.headers[NOT_TIP_KEY];
2423
if (connectivityResult == ConnectivityResult.none) {
25-
return _dio.resolve(new ResultData(Code.errorHandleFunction(Code.NETWORK_ERROR, "", noTip ?? false), false, Code.NETWORK_ERROR));
24+
return _dio.resolve(new ResultData(Code.errorHandleFunction(Code.NETWORK_ERROR, "", false), false, Code.NETWORK_ERROR));
2625
}
2726
return options;
2827
}
29-
30-
@override
31-
onError(DioError e) {
32-
Response errorResponse;
33-
var noTip = e.response != null ? e.response.headers[NOT_TIP_KEY] : true;
34-
if (e.response != null) {
35-
errorResponse = e.response;
36-
} else {
37-
errorResponse = new Response(statusCode: 666);
38-
}
39-
if (e.type == DioErrorType.CONNECT_TIMEOUT) {
40-
errorResponse.statusCode = Code.NETWORK_TIMEOUT;
41-
}
42-
return _dio.resolve(new ResultData(Code.errorHandleFunction(errorResponse.statusCode, e.message, noTip ?? false), false, errorResponse.statusCode));
43-
}
4428
}

lib/common/redux/gsy_state.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'package:flutter/material.dart';
22
import 'package:gsy_github_app_flutter/common/model/User.dart';
3+
import 'package:gsy_github_app_flutter/common/redux/middleware/epic_middleware.dart';
34
import 'package:gsy_github_app_flutter/common/redux/user_redux.dart';
45
import 'package:gsy_github_app_flutter/common/redux/theme_redux.dart';
56
import 'package:gsy_github_app_flutter/common/redux/locale_redux.dart';
7+
import 'package:redux/redux.dart';
68

79
/**
810
* Redux全局State
@@ -43,3 +45,10 @@ GSYState appReducer(GSYState state, action) {
4345
locale: LocaleReducer(state.locale, action),
4446
);
4547
}
48+
49+
50+
51+
final List<Middleware<GSYState>> middleware = [
52+
EpicMiddleware<GSYState>(UserInfoEpic()),
53+
UserInfoMiddleware(),
54+
];
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'dart:async';
2+
3+
import 'package:gsy_github_app_flutter/common/redux/middleware/epic.dart';
4+
import 'package:gsy_github_app_flutter/common/redux/middleware/epic_store.dart';
5+
import 'package:rxdart/streams.dart';
6+
7+
/// Combines a list of [Epic]s into one.
8+
///
9+
/// Rather than having one massive [Epic] that handles every possible type of
10+
/// action, it's best to break [Epic]s down into smaller, more manageable and
11+
/// testable units. This way we could have a `searchEpic`, a `chatEpic`,
12+
/// and an `updateProfileEpic`, for example.
13+
///
14+
/// However, the [EpicMiddleware] accepts only one [Epic]. So what are we to do?
15+
/// Fear not: redux_epics includes class for combining [Epic]s together!
16+
///
17+
/// Example:
18+
///
19+
/// final epic = combineEpics<State>([
20+
/// searchEpic,
21+
/// chatEpic,
22+
/// updateProfileEpic,
23+
/// ]);
24+
Epic<State> combineEpics<State>(List<Epic<State>> epics) {
25+
return (Stream<dynamic> actions, EpicStore<State> store) {
26+
return new MergeStream<dynamic>(epics.map((epic) => epic(actions, store)));
27+
};
28+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import 'dart:async';
2+
3+
import 'package:gsy_github_app_flutter/common/redux/middleware/epic_store.dart';
4+
5+
6+
/// A function that transforms one stream of actions into another
7+
/// stream of actions.
8+
///
9+
/// Actions in, actions out.
10+
///
11+
/// The best part: Epics are based on Dart Streams. This makes routine tasks
12+
/// easy, and complex tasks such as asynchronous error handling, cancellation,
13+
/// and debouncing a breeze. Once you're inside your Epic, use any stream
14+
/// patterns you desire as long as anything output from the final, returned
15+
/// stream, is an action. The actions you emit will be immediately dispatched
16+
/// through the rest of the middleware chain.
17+
///
18+
/// Epics run alongside the normal Redux dispatch channel, meaning you cannot
19+
/// accidentally "swallow" an incoming action. Actions always run through the
20+
/// rest of your middleware chain to your reducers before your Epics even
21+
/// receive the next action.
22+
///
23+
/// Note: Since the Actions you emit from your Epics are dispatched to your
24+
/// store, writing an Epic that simply returns the original actions Stream will
25+
/// result in an infinite loop. Do not do this!
26+
///
27+
/// ## Example
28+
///
29+
/// Let's say your app has a search box. When a user submits a search term,
30+
/// you dispatch a `PerformSearchAction` which contains the term. In order to
31+
/// actually listen for the `PerformSearchAction` and make a network request
32+
/// for the results, we can create an Epic!
33+
///
34+
/// In this instance, our Epic will need to filter all incoming actions it
35+
/// receives to only the `Action` it is interested in: the `PerformSearchAction`.
36+
/// Then, we need to make a network request using the provided search term.
37+
/// Finally, we need to transform those results into an action that contains
38+
/// the search results. If an error has occurred, we'll want to return an
39+
/// error action so our app can respond accordingly.
40+
///
41+
/// ### Code
42+
///
43+
/// Stream<dynamic> exampleEpic(
44+
/// Stream<dynamic> actions,
45+
/// EpicStore<State> store,
46+
/// ) {
47+
/// return actions
48+
/// .where((action) => action is PerformSearchAction)
49+
/// .asyncMap((action) =>
50+
/// // Pseudo api that returns a Future of SearchResults
51+
/// api.search((action as PerformSearch).searchTerm)
52+
/// .then((results) => new SearchResultsAction(results))
53+
/// .catchError((error) => new SearchErrorAction(error)));
54+
/// }
55+
typedef Stream<dynamic> Epic<State>(
56+
Stream<dynamic> actions, EpicStore<State> store);
57+
58+
/// A class that acts as an [Epic], transforming one stream of actions into
59+
/// another stream of actions. Generally, [Epic] functions are simpler, but
60+
/// you may have advanced use cases that require a type-safe class.
61+
///
62+
/// ### Example
63+
///
64+
/// class ExampleEpic extends EpicClass<State> {
65+
/// @override
66+
/// Stream<dynamic> map(Stream<dynamic> actions, EpicStore<State> store) {
67+
/// return actions
68+
/// .where((action) => action is PerformSearchAction)
69+
/// .asyncMap((action) =>
70+
/// // Pseudo api that returns a Future of SearchResults
71+
/// api.search((action as PerformSearch).searchTerm)
72+
/// .then((results) => new SearchResultsAction(results))
73+
/// .catchError((error) => new SearchErrorAction(error)));
74+
/// }
75+
/// }
76+
abstract class EpicClass<State> {
77+
Stream<dynamic> call(
78+
Stream<dynamic> actions,
79+
EpicStore<State> store,
80+
);
81+
}
82+
83+
/// An wrapper that allows you to create Epics which handle actions of a
84+
/// specific type, rather than all actions.
85+
///
86+
/// ### Example
87+
///
88+
/// Stream<dynamic> searchEpic(
89+
/// // Note: this epic only works with PerformSearchActions
90+
/// Stream<PerformSearchAction> actions,
91+
/// EpicStore<State> store,
92+
/// ) {
93+
/// return actions
94+
/// .asyncMap((action) =>
95+
/// api.search(action.searchTerm)
96+
/// .then((results) => new SearchResultsAction(results))
97+
/// .catchError((error) => new SearchErrorAction(error)));
98+
/// }
99+
///
100+
/// final epic = new TypedEpic<State, PerformSearchAction>(typedSearchEpic);
101+
///
102+
/// ### Combining Typed Epics
103+
///
104+
/// final epic = combineEpics([
105+
/// new TypedEpic<State, SearchAction>(searchEpic),
106+
/// new TypedEpic<State, ProfileAction>(profileEpic),
107+
/// new TypedEpic<State, ChatAction>(chatEpic),
108+
/// ]);
109+
class TypedEpic<State, Action> extends EpicClass<State> {
110+
final Stream<dynamic> Function(
111+
Stream<Action> actions,
112+
EpicStore<State> store,
113+
) epic;
114+
115+
TypedEpic(this.epic);
116+
117+
@override
118+
Stream<dynamic> call(Stream<dynamic> actions, EpicStore<State> store) {
119+
return epic(
120+
actions.transform(new StreamTransformer<dynamic, Action>.fromHandlers(
121+
handleData: (dynamic action, EventSink<Action> sink) {
122+
if (action is Action) {
123+
sink.add(action);
124+
}
125+
},
126+
)),
127+
store,
128+
);
129+
}
130+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import 'dart:async';
2+
3+
import 'package:gsy_github_app_flutter/common/redux/middleware/epic.dart';
4+
import 'package:gsy_github_app_flutter/common/redux/middleware/epic_store.dart';
5+
import 'package:redux/redux.dart';
6+
import 'package:rxdart/transformers.dart';
7+
8+
/// A [Redux](https://pub.dartlang.org/packages/redux) middleware that passes
9+
/// a stream of dispatched actions to the given [Epic].
10+
///
11+
/// It is recommended that you put your `EpicMiddleware` first when constructing
12+
/// the list of middleware for your store so any actions dispatched from
13+
/// your [Epic] will be intercepted by the remaining Middleware.
14+
///
15+
/// Example:
16+
///
17+
/// var epicMiddleware = new EpicMiddleware(new ExampleEpic());
18+
/// var store = new Store<List<Action>, Action>(reducer,
19+
/// initialState: [], middleware: [epicMiddleware]);
20+
class EpicMiddleware<State> extends MiddlewareClass<State> {
21+
final StreamController<dynamic> _actions =
22+
new StreamController<dynamic>.broadcast();
23+
final StreamController<Epic<State>> _epics =
24+
new StreamController.broadcast(sync: true);
25+
26+
final bool supportAsyncGenerators;
27+
Epic<State> _epic;
28+
bool _isSubscribed = false;
29+
30+
EpicMiddleware(Epic<State> epic, {this.supportAsyncGenerators = true})
31+
: _epic = epic;
32+
33+
@override
34+
call(Store<State> store, dynamic action, NextDispatcher next) {
35+
if (!_isSubscribed) {
36+
_epics.stream
37+
.transform<dynamic>(
38+
new SwitchMapStreamTransformer<Epic<State>, dynamic>(
39+
(epic) => epic(_actions.stream, new EpicStore(store))))
40+
.listen(store.dispatch);
41+
42+
_epics.add(_epic);
43+
44+
_isSubscribed = true;
45+
}
46+
47+
next(action);
48+
49+
if (supportAsyncGenerators) {
50+
// Future.delayed is an ugly hack to support async* functions.
51+
//
52+
// See: https://github.com/dart-lang/sdk/issues/33818
53+
new Future.delayed(Duration.zero, () {
54+
_actions.add(action);
55+
});
56+
} else {
57+
_actions.add(action);
58+
}
59+
}
60+
61+
/// Gets or replaces the epic currently used by the middleware.
62+
///
63+
/// Replacing epics is considered an advanced API. You might need this if your
64+
/// app grows large and want to instantiate Epics on the fly, rather than
65+
/// as a whole up front.
66+
Epic<State> get epic => _epic;
67+
68+
set epic(Epic<State> newEpic) {
69+
_epic = newEpic;
70+
71+
_epics.add(newEpic);
72+
}
73+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'dart:async';
2+
3+
import 'package:redux/redux.dart';
4+
5+
/// A stripped-down Redux [Store]. Removes unsupported [Store] methods.
6+
///
7+
/// Due to the way streams are implemented with Dart, it's impossible to
8+
/// perform `store.dispatch` from within an [Epic] or observe the store directly.
9+
class EpicStore<State> {
10+
final Store<State> _store;
11+
12+
EpicStore(this._store);
13+
14+
/// Returns the current state of the redux store
15+
State get state => _store.state;
16+
17+
Stream<State> get onChange => _store.onChange;
18+
}

0 commit comments

Comments
 (0)