mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Migrate form_app to null safety (#925)
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
// BSD-style license that can be found in the LICENSE file.
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import 'src/autofill.dart';
|
import 'src/autofill.dart';
|
||||||
import 'src/form_widgets.dart';
|
import 'src/form_widgets.dart';
|
||||||
@@ -11,9 +10,6 @@ import 'src/http/mock_client.dart';
|
|||||||
import 'src/sign_in_http.dart';
|
import 'src/sign_in_http.dart';
|
||||||
import 'src/validation.dart';
|
import 'src/validation.dart';
|
||||||
|
|
||||||
// Set up a mock HTTP client.
|
|
||||||
final http.Client httpClient = MockClient();
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const FormApp());
|
runApp(const FormApp());
|
||||||
}
|
}
|
||||||
@@ -23,7 +19,8 @@ final demos = [
|
|||||||
name: 'Sign in with HTTP',
|
name: 'Sign in with HTTP',
|
||||||
route: '/signin_http',
|
route: '/signin_http',
|
||||||
builder: (context) => SignInHttpDemo(
|
builder: (context) => SignInHttpDemo(
|
||||||
httpClient: httpClient,
|
// This sample uses a mock HTTP client.
|
||||||
|
httpClient: mockClient,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Demo(
|
Demo(
|
||||||
@@ -44,7 +41,7 @@ final demos = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
class FormApp extends StatelessWidget {
|
class FormApp extends StatelessWidget {
|
||||||
const FormApp({Key key}) : super(key: key);
|
const FormApp({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -58,7 +55,7 @@ class FormApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HomePage extends StatelessWidget {
|
class HomePage extends StatelessWidget {
|
||||||
const HomePage({Key key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -73,16 +70,16 @@ class HomePage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DemoTile extends StatelessWidget {
|
class DemoTile extends StatelessWidget {
|
||||||
final Demo demo;
|
final Demo? demo;
|
||||||
|
|
||||||
const DemoTile({this.demo, Key key}) : super(key: key);
|
const DemoTile({this.demo, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(demo.name),
|
title: Text(demo!.name),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pushNamed(context, demo.route);
|
Navigator.pushNamed(context, demo!.route);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -93,5 +90,5 @@ class Demo {
|
|||||||
final String route;
|
final String route;
|
||||||
final WidgetBuilder builder;
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
const Demo({this.name, this.route, this.builder});
|
const Demo({required this.name, required this.route, required this.builder});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
// Demonstrates how to use autofill hints. The full list of hints is here:
|
// Demonstrates how to use autofill hints. The full list of hints is here:
|
||||||
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart
|
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart
|
||||||
class AutofillDemo extends StatefulWidget {
|
class AutofillDemo extends StatefulWidget {
|
||||||
const AutofillDemo({Key key}) : super(key: key);
|
const AutofillDemo({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AutofillDemoState createState() => _AutofillDemoState();
|
_AutofillDemoState createState() => _AutofillDemoState();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:intl/intl.dart' as intl;
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
|
||||||
class FormWidgetsDemo extends StatefulWidget {
|
class FormWidgetsDemo extends StatefulWidget {
|
||||||
const FormWidgetsDemo({Key key}) : super(key: key);
|
const FormWidgetsDemo({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FormWidgetsDemoState createState() => _FormWidgetsDemoState();
|
_FormWidgetsDemoState createState() => _FormWidgetsDemoState();
|
||||||
@@ -18,7 +18,7 @@ class _FormWidgetsDemoState extends State<FormWidgetsDemo> {
|
|||||||
String description = '';
|
String description = '';
|
||||||
DateTime date = DateTime.now();
|
DateTime date = DateTime.now();
|
||||||
double maxValue = 0;
|
double maxValue = 0;
|
||||||
bool brushedTeeth = false;
|
bool? brushedTeeth = false;
|
||||||
bool enableFeature = false;
|
bool enableFeature = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -159,8 +159,8 @@ class _FormWidgetsDemoState extends State<FormWidgetsDemo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FormDatePicker<T> extends StatefulWidget {
|
class _FormDatePicker<T> extends StatefulWidget {
|
||||||
final DateTime date;
|
final DateTime? date;
|
||||||
final ValueChanged<T> onChanged;
|
final ValueChanged<T>? onChanged;
|
||||||
|
|
||||||
const _FormDatePicker({
|
const _FormDatePicker({
|
||||||
this.date,
|
this.date,
|
||||||
@@ -187,7 +187,7 @@ class _FormDatePickerState extends State<_FormDatePicker> {
|
|||||||
style: Theme.of(context).textTheme.bodyText1,
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
intl.DateFormat.yMd().format(widget.date),
|
intl.DateFormat.yMd().format(widget.date!),
|
||||||
style: Theme.of(context).textTheme.subtitle1,
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -197,7 +197,7 @@ class _FormDatePickerState extends State<_FormDatePicker> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var newDate = await showDatePicker(
|
var newDate = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: widget.date,
|
initialDate: widget.date!,
|
||||||
firstDate: DateTime(1900),
|
firstDate: DateTime(1900),
|
||||||
lastDate: DateTime(2100),
|
lastDate: DateTime(2100),
|
||||||
);
|
);
|
||||||
@@ -207,7 +207,7 @@ class _FormDatePickerState extends State<_FormDatePicker> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.onChanged(newDate);
|
widget.onChanged!(newDate);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,33 +1,18 @@
|
|||||||
// Copyright 2020, the Flutter 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:convert';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:http/testing.dart';
|
||||||
|
|
||||||
class MockClient extends Mock implements http.Client {
|
// Set up a mock HTTP client.
|
||||||
MockClient() {
|
final http.Client mockClient = MockClient(_mockHandler);
|
||||||
when(post('https://example.com/signin',
|
|
||||||
body: anyNamed('body'), headers: anyNamed('headers')))
|
|
||||||
.thenAnswer((answering) {
|
|
||||||
dynamic body = answering.namedArguments[const Symbol('body')];
|
|
||||||
|
|
||||||
if (body != null && body is String) {
|
Future<http.Response> _mockHandler(http.Request request) async {
|
||||||
var decodedJson = Map<String, dynamic>.from(
|
var decodedJson = Map<String, dynamic>.from(
|
||||||
json.decode(body) as Map<String, dynamic>);
|
json.decode(request.body) as Map<String, dynamic>);
|
||||||
|
|
||||||
if (decodedJson['email'] == 'root' &&
|
if (decodedJson['email'] == 'root' && decodedJson['password'] == 'password') {
|
||||||
decodedJson['password'] == 'password') {
|
return http.Response('', 200);
|
||||||
return Future.value(http.Response('', 200));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Future.value(http.Response('', 401));
|
|
||||||
});
|
|
||||||
|
|
||||||
when(post('https://example.com/signout'))
|
|
||||||
.thenAnswer((_) => Future.value(http.Response('', 401)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return http.Response('', 401);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ part 'sign_in_http.g.dart';
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class FormData {
|
class FormData {
|
||||||
String email;
|
String? email;
|
||||||
String password;
|
String? password;
|
||||||
|
|
||||||
FormData({
|
FormData({
|
||||||
this.email,
|
this.email,
|
||||||
@@ -27,11 +27,11 @@ class FormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SignInHttpDemo extends StatefulWidget {
|
class SignInHttpDemo extends StatefulWidget {
|
||||||
final http.Client httpClient;
|
final http.Client? httpClient;
|
||||||
|
|
||||||
const SignInHttpDemo({
|
const SignInHttpDemo({
|
||||||
this.httpClient,
|
this.httpClient,
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -80,8 +80,8 @@ class _SignInHttpDemoState extends State<SignInHttpDemo> {
|
|||||||
child: const Text('Sign in'),
|
child: const Text('Sign in'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// Use a JSON encoded string to send
|
// Use a JSON encoded string to send
|
||||||
var result = await widget.httpClient.post(
|
var result = await widget.httpClient!.post(
|
||||||
'https://example.com/signin',
|
Uri.parse('https://example.com/signin'),
|
||||||
body: json.encode(formData.toJson()),
|
body: json.encode(formData.toJson()),
|
||||||
headers: {'content-type': 'application/json'});
|
headers: {'content-type': 'application/json'});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// Copyright 2020, the Flutter 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.
|
|
||||||
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'sign_in_http.dart';
|
part of 'sign_in_http.dart';
|
||||||
@@ -12,8 +8,8 @@ part of 'sign_in_http.dart';
|
|||||||
|
|
||||||
FormData _$FormDataFromJson(Map<String, dynamic> json) {
|
FormData _$FormDataFromJson(Map<String, dynamic> json) {
|
||||||
return FormData(
|
return FormData(
|
||||||
email: json['email'] as String,
|
email: json['email'] as String?,
|
||||||
password: json['password'] as String,
|
password: json['password'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:english_words/english_words.dart' as english_words;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FormValidationDemo extends StatefulWidget {
|
class FormValidationDemo extends StatefulWidget {
|
||||||
const FormValidationDemo({Key key}) : super(key: key);
|
const FormValidationDemo({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FormValidationDemoState createState() => _FormValidationDemoState();
|
_FormValidationDemoState createState() => _FormValidationDemoState();
|
||||||
@@ -14,9 +14,9 @@ class FormValidationDemo extends StatefulWidget {
|
|||||||
|
|
||||||
class _FormValidationDemoState extends State<FormValidationDemo> {
|
class _FormValidationDemoState extends State<FormValidationDemo> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
String adjective;
|
String? adjective;
|
||||||
String noun;
|
String? noun;
|
||||||
bool agreedToTerms = false;
|
bool? agreedToTerms = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -32,7 +32,7 @@ class _FormValidationDemoState extends State<FormValidationDemo> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Validate the form by getting the FormState from the GlobalKey
|
// Validate the form by getting the FormState from the GlobalKey
|
||||||
// and calling validate() on it.
|
// and calling validate() on it.
|
||||||
var valid = _formKey.currentState.validate();
|
var valid = _formKey.currentState!.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ class _FormValidationDemoState extends State<FormValidationDemo> {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
return 'Please enter an adjective.';
|
return 'Please enter an adjective.';
|
||||||
}
|
}
|
||||||
if (english_words.adjectives.contains(value)) {
|
if (english_words.adjectives.contains(value)) {
|
||||||
@@ -92,7 +92,7 @@ class _FormValidationDemoState extends State<FormValidationDemo> {
|
|||||||
// A text field that validates that the text is a noun.
|
// A text field that validates that the text is a noun.
|
||||||
TextFormField(
|
TextFormField(
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
return 'Please enter a noun.';
|
return 'Please enter a noun.';
|
||||||
}
|
}
|
||||||
if (english_words.nouns.contains(value)) {
|
if (english_words.nouns.contains(value)) {
|
||||||
@@ -151,7 +151,7 @@ class _FormValidationDemoState extends State<FormValidationDemo> {
|
|||||||
formFieldState.errorText ?? "",
|
formFieldState.errorText ?? "",
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.caption
|
.caption!
|
||||||
.copyWith(color: Theme.of(context).errorColor),
|
.copyWith(color: Theme.of(context).errorColor),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ packages:
|
|||||||
name: http
|
name: http
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.2"
|
version: "0.13.4"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -248,7 +248,7 @@ packages:
|
|||||||
name: http_parser
|
name: http_parser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "4.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -319,13 +319,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
mockito:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: mockito
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.7"
|
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -374,7 +367,7 @@ packages:
|
|||||||
name: shelf
|
name: shelf
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "1.2.0"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -486,4 +479,4 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.12.0 <3.0.0"
|
dart: ">=2.14.0 <3.0.0"
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ publish_to: "none"
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.10.0 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: ^1.0.0
|
cupertino_icons: ^1.0.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
http: ^0.12.0
|
http: ^0.13.0
|
||||||
mockito: ^5.0.0
|
|
||||||
json_annotation: any
|
json_annotation: any
|
||||||
english_words: ^4.0.0
|
english_words: ^4.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:form_app/src/http/mock_client.dart';
|
import 'package:form_app/src/http/mock_client.dart';
|
||||||
import 'package:form_app/src/sign_in_http.dart';
|
import 'package:form_app/src/sign_in_http.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
final http.Client httpClient = MockClient();
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('sign in', (tester) async {
|
testWidgets('sign in', (tester) async {
|
||||||
@@ -28,7 +25,7 @@ void main() {
|
|||||||
Future<void> _signIn(WidgetTester tester, String email, String password) async {
|
Future<void> _signIn(WidgetTester tester, String email, String password) async {
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: SignInHttpDemo(
|
home: SignInHttpDemo(
|
||||||
httpClient: httpClient,
|
httpClient: mockClient,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user