安装 Flutter 包
http包,消耗HTTP资源。
shared_preferences包,为简单数据提供持久存储。
将包添加到您的pubspec.yaml文件中
dependencies:
**shared_preferences: ^0.5.6+1
****http: ^0.12.0+4**
运行命令flutter pub get
注意:您还可以选择使用SQLite或Hive包处理数据存储,特别是对于大型数据结构。
像这样创建文件夹结构:
在lib目录中,创建两个文件夹:
network_utils和
screen 。
在 screen 目录,创建 login.dart, home.dart, register.dart 并创造 api.dart 文件输入 network_utils 文件夹。
在api.dart文件中,编写一个管理所有API方法的Network类
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class Network{
final String _url = 'http://localhost:8000/api/v1';
//if you are using android studio emulator, change localhost to 10.0.2.2
var token;
_getToken() async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
token = jsonDecode(localStorage.getString('token'))['token'];
}
authData(data, apiUrl) async {
var fullUrl = _url + apiUrl;
return await http.post(
fullUrl,
body: jsonEncode(data),
headers: _setHeaders()
);
}
getData(apiUrl) async {
var fullUrl = _url + apiUrl;
await _getToken();
return await http.get(
fullUrl,
headers: _setHeaders()
);
}
_setHeaders() => {
'Content-type' : 'application/json',
'Accept' : 'application/json',
'Authorization' : 'Bearer $token'
};
}
在Network类中,定义 String _url = 'http://localhost:8000/api/v1 这基本上是我们Laravel API 的 BASE_URL。
如果您使用的是 android 模拟器,请将localhost:8000更改为10.0.2.2:8000
另外,定义一个变量token,这将存储用于身份验证的 API 令牌,并将附加到每个需要身份验证的请求。
接下来我们写一个异步函数 _getToken
_getToken()async{
SharedPreferences localStorage = await SharedPreferences.*getInstance*();
token = jsonDecode(localStorage.getString('token'))['token'];
}
该函数检查用户设备中存储的令牌并将其分配给最初定义的String token;
我们使用SharedPreferences来获取设备的localStorage实例,在其中使用_getString方法检索登录或注册期间存储的密钥token 。 使用jsonDecode包装此方法,将检索到的键/值对转换为数组。这样,我们就可以访问值['token'] 。
authData()函数是一个异步函数,用于处理对我们的登录和注册API 端点的所有post请求,它需要两个参数: data和apiUrl 。
我们通过附加 baseUrl _url和传递给函数的apiUrl来构建fullUrl , http.post()方法采用fullUrl 、 body和headers来发出POST请求。 authData函数返回此请求的响应。
您可能已经观察到,对于headers参数,我们调用_setHeaders()来传递标头,如下所示:
_setHeaders() => {
'Content-type' : 'application/json',
'Accept' : 'application/json',
'Authorization' : 'Bearer $token'
};
这使得它可重用且更干净,因为我们在发出请求时必须到处调用它。
接下来是getData()函数,这也是一个带有参数apiUrl的async函数,如果您的查询需要传递一组参数,您可以添加更多参数,例如data 。就本文而言,我们的GET请求不带额外参数。
在getData()函数中,我们构建类似于postData fullUrl并调用_getToken()以确保设置了token ,否则它将为空,并且我们的端点将返回unauthorized我们没有为authData()这样做方法,因为注册和登录路由不需要身份验证。
http.get()方法帮助发送我们的GET请求,这里没有传递body参数。
这是我们应用程序的主要入口点,在这里我们将检查用户是否经过身份验证并返回登录屏幕或主屏幕。
import 'package:flutter/material.dart';
import 'package:tutorial_app/screen/login.dart';
import 'package:tutorial_app/screen/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
debugShowCheckedModeBanner: false,
home: CheckAuth(),
);
}
}
class CheckAuth extends StatefulWidget {
@override
_CheckAuthState createState() => _CheckAuthState();
}
class _CheckAuthState extends State<CheckAuth> {
bool isAuth = false;
@override
void initState() {
_checkIfLoggedIn();
super.initState();
}
void _checkIfLoggedIn() async{
SharedPreferences localStorage = await SharedPreferences.getInstance();
var token = localStorage.getString('token');
if(token != null){
setState(() {
isAuth = true;
});
}
}
@override
Widget build(BuildContext context) {
Widget child;
if (isAuth) {
child = Home();
} else {
child = Login();
}
return Scaffold(
body: child,
);
}
}
_CheckAuthState 我们定义一个布尔值 isAuth = false; 在 initState() 我们打电话 _checkIfLoggedIn 这个方法检查 localStorage 的可用性 token 和集 isAuth = true 像这样:
void _checkIfLoggedIn() async{
SharedPreferences localStorage = await SharedPreferences.*getInstance*();
var token = localStorage.getString('token');
if(token != null){
setState(() {
isAuth = true;
});
}
}
最后,在构建方法中,我们检查是否isAuth = true并返回Home() else Login()
我们就在这里,如果到目前为止您没有遇到任何错误,请 注册 并 登录 。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:tutorial_app/network_utils/api.dart';
import 'package:tutorial_app/screen/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tutorial_app/screen/register.dart';
class Login extends StatefulWidget {
@override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
bool _isLoading = false;
final _formKey = GlobalKey<FormState>();
var email;
var password;
final _scaffoldKey = GlobalKey<ScaffoldState>();
_showMsg(msg) {
final snackBar = SnackBar(
content: Text(msg),
action: SnackBarAction(
label: 'Close',
onPressed: () {
// Some code to undo the change!
},
),
);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
@override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Scaffold(
key: _scaffoldKey,
body: Container(
color: Colors.teal,
child: Stack(
children: <Widget>[
Positioned(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
elevation: 4.0,
color: Colors.white,
margin: EdgeInsets.only(left: 20, right: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.email,
color: Colors.grey,
),
hintText: "Email",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (emailValue) {
if (emailValue.isEmpty) {
return 'Please enter email';
}
email = emailValue;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Colors.grey,
),
hintText: "Password",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (passwordValue) {
if (passwordValue.isEmpty) {
return 'Please enter some text';
}
password = passwordValue;
return null;
},
),
Padding(
padding: const EdgeInsets.all(10.0),
child: FlatButton(
child: Padding(
padding: EdgeInsets.only(
top: 8, bottom: 8, left: 10, right: 10),
child: Text(
_isLoading? 'Proccessing...' : 'Login',
textDirection: TextDirection.ltr,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
color: Colors.teal,
disabledColor: Colors.grey,
shape: new RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(20.0)),
onPressed: () {
if (_formKey.currentState.validate()) {
_login();
}
},
),
),
],
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: InkWell(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Register()));
},
child: Text(
'Create new Account',
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
),
],
),
),
)
],
),
),
);
}
void _login() async{
setState(() {
_isLoading = true;
});
var data = {
'email' : email,
'password' : password
};
var res = await Network().authData(data, '/login');
var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString('token', json.encode(body['token']));
localStorage.setString('user', json.encode(body['user']));
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Home()
),
);
}else{
_showMsg(body['message']);
}
setState(() {
_isLoading = false;
});
}
}
在 Login 类中,我们首先使用Form Widget 构建一个带有输入验证的基本登录表单。
定义一个布尔值_isLoading = false;我们将使用它来将login按钮的状态更改为processing...
此外,定义email和password变量,在验证后从各自的表单字段设置它们的值。我们将定义_formKey和_scaffoldKey如下
final _formKey = GlobalKey<FormState>();This uniquely identifies the Form , and allows validation of the form in a later step. final _scaffoldKey = GlobalKey<ScaffoldState>();Assign a GlobalKey to the Scaffold, then use the key.currentState property to obtain the ScaffoldState for a snackBar later.
现在,在构建小部件下方,我们编写 async _login()方法,如下所示:
void _login() async{
setState(() {
_isLoading = true;
});
var data = {
'email' : email,
'password' : password
};
var res = await Network().authData(data, '/login');
var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.*getInstance*();
localStorage.setString('token', json.encode(body['token']));
localStorage.setString('user', json.encode(body['user']));
Navigator.*push*(
context,
new MaterialPageRoute(
builder: (context) => Home()
),
);
}else{
_showMsg(body['message']);
}
setState(() {
_isLoading = false;
});
}
我们设置 _isLoading = true将login按钮更改 为processing...
收集表单值并构建一个data对象,该数据对象将传递给我们的 API 调用
var res = await Network().authData(data, '/login) 定义一个 var res = await Network().authData(data, '/login)
在这里,我们对authData()方法进行API 调用,该方法最初是在Network类中的api.dart文件中创建的,还记得它需要两个参数吗?因此,我们传入data和API 路由'/login' 。
一旦我们的后端服务收到响应,它将存储在res变量中,然后我们将此 JSON res.body解码为 var body = json.decode(res.body)
使用if 条件语句,我们检查body['success']这是来自我们 API 的布尔值,如果为 true ,那么我们已成功通过应用程序的身份验证,因此,我们使用SharedPreferences为token和user设置localStorage值,如下所示在片段中。使用Navigator.push我们导航到Home()路线。
否则,如果我们的端点出现错误,我们将调用_showMsg()并传入消息body['message']这将触发包含错误消息的SnackBar通知。
最后,我们设置_isLoading = false 。
register.dart与login.dart 类似, 显着的变化在于表单字段的数量以及 API 端点'/register' ,如下所示:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:tutorial_app/network_utils/api.dart';
import 'package:tutorial_app/screen/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tutorial_app/screen/login.dart';
class Register extends StatefulWidget {
@override
_RegisterState createState() => _RegisterState();
}
class _RegisterState extends State<Register> {
bool _isLoading = false;
final _formKey = GlobalKey<FormState>();
var email;
var password;
var fname;
var lname;
var phone;
@override
Widget build(BuildContext context) {
return Material(
child: Container(
color: Colors.teal,
child: Stack(
children: <Widget>[
Positioned(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
elevation: 4.0,
color: Colors.white,
margin: EdgeInsets.only(left: 20, right: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.email,
color: Colors.grey,
),
hintText: "Email",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (emailValue) {
if (emailValue.isEmpty) {
return 'Please enter email';
}
email = emailValue;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.insert_emoticon,
color: Colors.grey,
),
hintText: "First Name",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (firstname) {
if (firstname.isEmpty) {
return 'Please enter your first name';
}
fname = firstname;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.insert_emoticon,
color: Colors.grey,
),
hintText: "Last Name",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (lastname) {
if (lastname.isEmpty) {
return 'Please enter your last name';
}
lname = lastname;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.phone,
color: Colors.grey,
),
hintText: "Phone",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (phonenumber) {
if (phonenumber.isEmpty) {
return 'Please enter phone number';
}
phone = phonenumber;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Colors.grey,
),
hintText: "Password",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (passwordValue) {
if (passwordValue.isEmpty) {
return 'Please enter some text';
}
password = passwordValue;
return null;
},
),
Padding(
padding: const EdgeInsets.all(10.0),
child: FlatButton(
child: Padding(
padding: EdgeInsets.only(
top: 8, bottom: 8, left: 10, right: 10),
child: Text(
_isLoading? 'Proccessing...' : 'Register',
textDirection: TextDirection.ltr,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
color: Colors.teal,
disabledColor: Colors.grey,
shape: new RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(20.0)),
onPressed: () {
if (_formKey.currentState.validate()) {
_register();
}
},
),
),
],
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: InkWell(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Login()));
},
child: Text(
'Already Have an Account',
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
),
],
),
),
)
],
),
),
);
}
void _register()async{
setState(() {
_isLoading = true;
});
var data = {
'email' : email,
'password': password,
'phone': phone,
'fname': fname,
'lname': lname
};
var res = await Network().authData(data, '/register');
var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString('token', json.encode(body['token']));
localStorage.setString('user', json.encode(body['user']));
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Home()
),
);
}
setState(() {
_isLoading = false;
});
}
}
lib/main.dart在这里,我们将检索存储在localStorage中的用户数据,并实现注销操作。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:tutorial_app/screen/login.dart';
import 'package:tutorial_app/network_utils/api.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home>{
String name;
@override
void initState(){
_loadUserData();
super.initState();
}
_loadUserData() async{
SharedPreferences localStorage = await SharedPreferences.getInstance();
var user = jsonDecode(localStorage.getString('user'));
if(user != null) {
setState(() {
name = user['fname'];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test App'),
backgroundColor: Colors.teal,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Hi, $name',
style: TextStyle(
fontWeight: FontWeight.bold
),
),
Center(
child: RaisedButton(
elevation: 10,
onPressed: (){
logout();
},
color: Colors.teal,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
child: Text('Logout'),
),
),
],
),
),
);
}
void logout() async{
var res = await Network().getData('/logout');
var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.remove('user');
localStorage.remove('token');
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>Login()));
}
}
}
在initState() 方法中,我们调用 _loadUserData返回注册/登录 期间存储在localStorage 中的用户数据 ,
_loadUserData() async{
SharedPreferences localStorage = await SharedPreferences.*getInstance*();
var user = jsonDecode(localStorage.getString('user'));
if(user != null) {
setState(() {
name = user['fname'];
});
}
}
您可以将此用户数据设置为任何声明为保存用户数据的变量,我将user['fname'] 设置为 String name ,这稍后将在 第 40 行的Text小部件中使用,
接下来,我们使用RaisedButton小部件创建一个注销按钮 ,并onPressed调用logout() 方法。
logout 方法调用Network类的getData方法,并在收到成功响应后传递apiUrl /logout ,表明该令牌已被我们的后端护照服务失效,我们在localStorage上调用.remove()方法,这是一个SharedPreferences 的实例中, 我们传入token密钥并对user执行相同的操作并导航到Login()屏幕。