-
Notifications
You must be signed in to change notification settings - Fork 356
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ok_http
: Add BaseClient Implementation and make asynchronous reques…
…ts. (#1215)
- Loading branch information
1 parent
6337ee3
commit 7bfbeea
Showing
24 changed files
with
8,853 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
## 0.1.0-wip | ||
|
||
* Initial release. | ||
- Implementation of [`BaseClient`](https://pub.dev/documentation/http/latest/http/BaseClient-class.html) and `send()` method using [`enqueue()` API](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-call/enqueue.html) | ||
- `ok_http` can now send asynchronous requests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
include: ../../analysis_options.yaml | ||
|
||
analyzer: | ||
exclude: | ||
- lib/src/third_party/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'package:http_client_conformance_tests/http_client_conformance_tests.dart'; | ||
import 'package:integration_test/integration_test.dart'; | ||
import 'package:ok_http/ok_http.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
void main() async { | ||
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); | ||
|
||
await testConformance(); | ||
} | ||
|
||
Future<void> testConformance() async { | ||
group('ok_http client', () { | ||
testRequestBody(OkHttpClient()); | ||
testResponseBody(OkHttpClient(), canStreamResponseBody: false); | ||
testRequestHeaders(OkHttpClient()); | ||
testRequestMethods(OkHttpClient(), preservesMethodCase: true); | ||
testResponseStatusLine(OkHttpClient()); | ||
testCompressedResponseBody(OkHttpClient()); | ||
testIsolate(OkHttpClient.new); | ||
testResponseCookies(OkHttpClient(), canReceiveSetCookieHeaders: true); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
class Book { | ||
String title; | ||
String description; | ||
Uri imageUrl; | ||
|
||
Book(this.title, this.description, this.imageUrl); | ||
|
||
static List<Book> listFromJson(Map<dynamic, dynamic> json) { | ||
final books = <Book>[]; | ||
|
||
if (json['items'] case final List<dynamic> items) { | ||
for (final item in items) { | ||
if (item case {'volumeInfo': final Map<dynamic, dynamic> volumeInfo}) { | ||
if (volumeInfo | ||
case { | ||
'title': final String title, | ||
'description': final String description, | ||
'imageLinks': {'smallThumbnail': final String thumbnail} | ||
}) { | ||
books.add(Book(title, description, Uri.parse(thumbnail))); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return books; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,149 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'dart:async'; | ||
import 'package:http/http.dart'; | ||
import 'package:http/io_client.dart'; | ||
import 'package:http_image_provider/http_image_provider.dart'; | ||
import 'package:ok_http/ok_http.dart'; | ||
import 'package:provider/provider.dart'; | ||
|
||
import 'book.dart'; | ||
|
||
void main() { | ||
runApp(const MyApp()); | ||
final Client httpClient; | ||
if (Platform.isAndroid) { | ||
httpClient = OkHttpClient(); | ||
} else { | ||
httpClient = IOClient(HttpClient()..userAgent = 'Book Agent'); | ||
} | ||
|
||
runApp(Provider<Client>( | ||
create: (_) => httpClient, | ||
child: const BookSearchApp(), | ||
dispose: (_, client) => client.close())); | ||
} | ||
|
||
class MyApp extends StatefulWidget { | ||
const MyApp({super.key}); | ||
class BookSearchApp extends StatelessWidget { | ||
const BookSearchApp({super.key}); | ||
|
||
@override | ||
State<MyApp> createState() => _MyAppState(); | ||
Widget build(BuildContext context) => const MaterialApp( | ||
// Remove the debug banner. | ||
debugShowCheckedModeBanner: false, | ||
title: 'Book Search', | ||
home: HomePage(), | ||
); | ||
} | ||
|
||
class _MyAppState extends State<MyApp> { | ||
late int sumResult; | ||
late Future<int> sumAsyncResult; | ||
class HomePage extends StatefulWidget { | ||
const HomePage({super.key}); | ||
|
||
@override | ||
State<HomePage> createState() => _HomePageState(); | ||
} | ||
|
||
class _HomePageState extends State<HomePage> { | ||
List<Book>? _books; | ||
String? _lastQuery; | ||
late Client _client; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
_client = context.read<Client>(); | ||
} | ||
|
||
// Get the list of books matching `query`. | ||
// The `get` call will automatically use the `client` configured in `main`. | ||
Future<List<Book>> _findMatchingBooks(String query) async { | ||
final response = await _client.get( | ||
Uri.https( | ||
'www.googleapis.com', | ||
'/books/v1/volumes', | ||
{'q': query, 'maxResults': '20', 'printType': 'books'}, | ||
), | ||
); | ||
|
||
final json = jsonDecode(utf8.decode(response.bodyBytes)) as Map; | ||
return Book.listFromJson(json); | ||
} | ||
|
||
void _runSearch(String query) async { | ||
_lastQuery = query; | ||
if (query.isEmpty) { | ||
setState(() { | ||
_books = null; | ||
}); | ||
return; | ||
} | ||
|
||
final books = await _findMatchingBooks(query); | ||
// Avoid the situation where a slow-running query finishes late and | ||
// replaces newer search results. | ||
if (query != _lastQuery) return; | ||
setState(() { | ||
_books = books; | ||
}); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
const textStyle = TextStyle(fontSize: 25); | ||
const spacerSmall = SizedBox(height: 10); | ||
return MaterialApp( | ||
home: Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Native Packages'), | ||
), | ||
body: SingleChildScrollView( | ||
child: Container( | ||
padding: const EdgeInsets.all(10), | ||
child: Column( | ||
children: [ | ||
const Text( | ||
'', | ||
style: textStyle, | ||
textAlign: TextAlign.center, | ||
), | ||
spacerSmall, | ||
Text( | ||
'sum(1, 2) = $sumResult', | ||
style: textStyle, | ||
textAlign: TextAlign.center, | ||
), | ||
spacerSmall, | ||
FutureBuilder<int>( | ||
future: sumAsyncResult, | ||
builder: (BuildContext context, AsyncSnapshot<int> value) { | ||
final displayValue = | ||
(value.hasData) ? value.data : 'loading'; | ||
return Text( | ||
'await sumAsync(3, 4) = $displayValue', | ||
style: textStyle, | ||
textAlign: TextAlign.center, | ||
); | ||
}, | ||
), | ||
], | ||
final searchResult = _books == null | ||
? const Text('Please enter a query', style: TextStyle(fontSize: 24)) | ||
: _books!.isNotEmpty | ||
? BookList(_books!) | ||
: const Text('No results found', style: TextStyle(fontSize: 24)); | ||
|
||
return Scaffold( | ||
appBar: AppBar(title: const Text('Book Search')), | ||
body: Padding( | ||
padding: const EdgeInsets.all(10), | ||
child: Column( | ||
children: [ | ||
const SizedBox(height: 20), | ||
TextField( | ||
onChanged: _runSearch, | ||
decoration: const InputDecoration( | ||
labelText: 'Search', | ||
suffixIcon: Icon(Icons.search), | ||
), | ||
), | ||
), | ||
const SizedBox(height: 20), | ||
Expanded(child: searchResult), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class BookList extends StatefulWidget { | ||
final List<Book> books; | ||
const BookList(this.books, {super.key}); | ||
|
||
@override | ||
State<BookList> createState() => _BookListState(); | ||
} | ||
|
||
class _BookListState extends State<BookList> { | ||
@override | ||
Widget build(BuildContext context) => ListView.builder( | ||
itemCount: widget.books.length, | ||
itemBuilder: (context, index) => Card( | ||
key: ValueKey(widget.books[index].title), | ||
child: ListTile( | ||
leading: Image( | ||
image: HttpImage( | ||
widget.books[index].imageUrl.replace(scheme: 'https'), | ||
client: context.read<Client>())), | ||
title: Text(widget.books[index].title), | ||
subtitle: Text(widget.books[index].description), | ||
), | ||
), | ||
); | ||
} |
Oops, something went wrong.