今日的程序码 => GTIHBU
今天来讲讲搜寻的介绍。那当然,肯定要以 github 搜寻为范例啦。哈哈哈~~
今天会用到
程序码参考来自 => RxDart 范例
可以看到,这边有一个变数叫做 cache,用来暂时储存快取的资料,是一个 Map 的型态 Map<搜寻的字, 搜寻结果>,当我们要 fetch api 时,可以先去判断 cache 里面有没有这笔资料的值,这样子。
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
class GithubApi {
/// url
final String baseUrl;
/// 快取,<搜寻的字, 搜寻结果>
final Map<String, SearchResult> cache;
/// http client
final http.Client _client;
GithubApi({
http.Client? client,
Map<String, SearchResult>? cache,
this.baseUrl = 'https://api.github.com/search/repositories?q=',
}) : _client = client ?? http.Client(),
cache = cache ?? <String, SearchResult>{};
/// Search Github for repositories using the given term
/// 处理快取
Future<SearchResult> search(String term) async {
final cached = cache[term];
if (cached != null) {
return cached;
} else {
final result = await _fetchResults(term);
cache[term] = result;
return result;
}
}
/// 请求 api
Future<SearchResult> _fetchResults(String term) async {
final response = await _client.get(Uri.parse('$baseUrl$term'));
final results = json.decode(response.body);
return SearchResult.fromJson(results['items']);
}
}
class SearchResult {
final List<SearchResultItem> items;
SearchResult(this.items);
factory SearchResult.fromJson(dynamic json) {
final items = (json as List)
.map((item) => SearchResultItem.fromJson(item))
.toList(growable: false);
return SearchResult(items);
}
bool get isPopulated => items.isNotEmpty;
/// 定义 isEmpty,这样就不用使用 SearchResult.items.isEmpty
bool get isEmpty => items.isEmpty;
}
class SearchResultItem {
final String fullName;
final String url;
final String avatarUrl;
SearchResultItem(this.fullName, this.url, this.avatarUrl);
factory SearchResultItem.fromJson(Map<String, dynamic> json) {
return SearchResultItem(
json['full_name'] as String,
json['html_url'] as String,
(json['owner'] as Map<String, dynamic>)['avatar_url'] as String,
);
}
}
这边先去 New 一个 GitHubApi 出来,之後用来在 SearchScreen 实作 bloc。
void main() => runApp(SearchApp(api: GithubApi()));
class SearchApp extends StatelessWidget {
final GithubApi api;
const SearchApp({Key? key, required this.api}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RxDart Github Search',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.grey,
),
home: SearchScreen(api: api),
);
}
}
这边是一个放逻辑的地方。
可以看到我们使用了 RxDart
Rxdart
相信这边看官网会比较清楚的(ㄅ~~
可以看到我们使用了一个 PublishSubject,他是一个类似 stream 的 broadcast 效果。
下面用到的一些函示都是 RxDart 帮我们整合的函式。
更多的资料可以看 Rx Function 文件
class SearchBloc {
final Sink<String> onTextChanged;
final Stream<SearchState> state;
/// 这边用 factory 的目的,是为了让这个 SearchBloc 参数一样时,物件判定会是一样的。
/// 虽然传递的参数只有一个,但是实际上我们要创建这个 SearchBloc 需要两个参数。
///
/// 建构子前以关键字 factory 宣告一个工厂建构子,工厂建构子不一定会产生一个新物件,可能回传一个既存物件。
/// 要注意工厂建构子在return之前还未有实体,故不能使用this引用成员变数的值或呼叫函数。
factory SearchBloc(GithubApi api) {
final onTextChanged = PublishSubject<String>();
final state = onTextChanged
// If the text has not changed, do not perform a new search
// 如果与前一比资料一样,将不会触发。
.distinct()
// Wait for the user to stop typing for 250ms before running a search
// 等 0.25 秒後,才开始搜寻,执行 api
.debounceTime(const Duration(milliseconds: 250))
// Call the Github api with the given search term and convert it to a
// State. If another search term is entered, switchMap will ensure
// the previous search is discarded so we don't deliver stale results
// to the View.
// 如果输入 a 的时候,开始搜寻,再输入 b 後,变成 ab,但是 a 的搜寻结果还没出来,那麽变成 ab 後,他会把这个还没搜寻完成的 a 流程给停止掉。避免浪费搜寻时间。
.switchMap<SearchState>((String term) => _search(term, api))
// The initial state to deliver to the screen.
// 在这个 stream 的最前面加上一个初始值
.startWith(SearchNoTerm());
// 这边已经初始化完成了,在第 15 行、17 行
return SearchBloc._(onTextChanged, state);
}
SearchBloc._(this.onTextChanged, this.state);
// 给画面 call 的,好让这个 dispose 掉。
void dispose() {
onTextChanged.close();
}
static Stream<SearchState> _search(String term, GithubApi api) => term.isEmpty
? Stream.value(SearchNoTerm())
// when the future completes, this stream will fire one event, either data or error, and then close with a done-event.
// Rx.fromCallable,它在侦听时调用您指定的函数,然後发出从该函数返回的值。这整个 Rx.fromCallable(()=>future) 会是一个 Stream
: Rx.fromCallable(() => api.search(term))
.map((result) =>
result.isEmpty? SearchEmpty() : SearchPopulated(result))
.startWith(SearchLoading())
.onErrorReturn(SearchError());
}
class SearchState {}
class SearchLoading extends SearchState {}
class SearchError extends SearchState {}
class SearchNoTerm extends SearchState {}
class SearchPopulated extends SearchState {
final SearchResult result;
SearchPopulated(this.result);
}
class SearchEmpty extends SearchState {}
class SearchScreen extends StatefulWidget {
final GithubApi api;
const SearchScreen({Key? key, required this.api}) : super(key: key);
@override
SearchScreenState createState() {
return SearchScreenState();
}
}
class SearchScreenState extends State<SearchScreen> {
late final SearchBloc bloc;
@override
void initState() {
super.initState();
bloc = SearchBloc(widget.api);
}
@override
void dispose() {
bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<SearchState>(
stream: bloc.state,
initialData: SearchNoTerm(),
builder: (BuildContext context, AsyncSnapshot<SearchState> snapshot) {
final state = snapshot.requireData;
return Scaffold(
body: Stack(
children: <Widget>[
Flex(direction: Axis.vertical, children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(16.0, 24.0, 16.0, 4.0),
child: TextField(
decoration: const InputDecoration(
border: InputBorder.none,
hintText: 'Search Github...',
),
style: const TextStyle(
fontSize: 36.0,
fontFamily: 'Hind',
decoration: TextDecoration.none,
),
onChanged: bloc.onTextChanged.add,
),
),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _buildChild(state),
),
)
])
],
),
);
},
);
}
Widget _buildChild(SearchState state) {
print(state);
if (state is SearchNoTerm) {
return const SearchIntro();
} else if (state is SearchEmpty) {
return const EmptyWidget();
} else if (state is SearchLoading) {
return const LoadingWidget();
} else if (state is SearchError) {
return const SearchErrorWidget();
} else if (state is SearchPopulated) {
return SearchResultWidget(items: state.result.items);
}
throw Exception('${state.runtimeType} is not supported');
}
}
什麽是物件导向? 为什麽需要物件导向? 物件导向重要在什麽地方? 要回答第一个问题前,必须先回答一...
请先安装Postman 今天完成整个CRUD,简单介绍操作Postman。 接续昨天文章 9.修改r...
SphereFace 在2017年发表在CVPR的文章,改进原先使用softmax作为loss fu...
举例: 想像你的产品有个/user/email route允许post request去修改已经认证...
Ascii - 产生 3D 旋转甜甜圈的甜甜圈形 C 程序码参考笔记 参考资料 参考资料: Donu...