เนื้อหาต่อไปนี้เป็นการประยุกต์ใช้งาน ต่อเนื้อง
จากเนื้อหาตอนที่แล้ว เกี่ยวกับระบบสมาชิก ซึ่งตอน
ที่แล้วเราแค่จำลองข้อมูลที่เครื่องโดยใช้ชนิดข้อมูล
SharedPreferences ทบทวนตอนที่แล้วได้ที่
ประยุกต์เก็บข้อมูลด้วย shared preferences ใน Flutter http://niik.in/1059
https://www.ninenik.com/content.php?arti_id=1059 via @ninenik
เนื้อหาในตอนนี้จะมีการปรับโค้ดเล็กน้อยในไฟล์ login register และ profile
แต่จะมีส่วนต่างๆ ที่เสริมเพิ่มเติมเข้ามาค่อยข้างซับซ้อน เพราะเป็นเนื้อหาจากบทความต่างๆ
ที่เราเคยใช้งานมาแล้ว ไม่ว่าจะเป็นการใช้งาน provider การใช้งาน http เพื่อเชื่อมต่อกับ
server รวมถึงการใช้งาน SharedPreferences ที่ใช้ในตอนที่แล้วด้วย นอกจากนั้น เรายังมีการ
กำหนดหรือใช้งาน API จากฝั่ง server ซึ่งได้เขียนบทความเป็นแนวทางไว้แล้วตามลิ้งค์ด้านล่าง
สร้าง REST API ระบบ Login ด้วย Slim framework 4 http://niik.in/1058
https://www.ninenik.com/content.php?arti_id=1058 via @ninenik
อธิบายสิ่งที่จะทำ
เราจะมี api สำหรับระบบสมาชิก ที่ทำหน้าที่ สร้างบัญชีผู้ใช้ใหม่ ตรวจสอบการล็อกอินเข้าสู่ระบบ
และดึงข้อมูลสมาชิกจากฐานข้อมูล MySQL บน server มาแสดง ในที่นี้เราจำลองที่เครื่อง local
แนวทาง api ที่เราใช้จะเป็นตามลิ้งค์ดังนี้ http://niik.in/1058
ในฝั่ง flutter หรือ app ของเราก็จะทำการใช้งานรูปแบบฟอร์มทั้งหน้าสมัครสมาชิก และหน้าล็อกอิน
จากตอนที่แล้วมาประยุกต์ โดยส่งข้อมูลจากฟอร์มผ่านการใช้งาน provider ไปทำการบันทึกข้อมูลในฐาน
ข้อมูลบน server และทำการส่งข้อมูลการล็อกอินไปยัง server เพื่อตรวจสอบการล็อกอินเข้าใช้งาน
เมื่อล็อกอินเข้าระบบผ่าน ก็จะทำการดึงข้อมูลผู้ใช้มาแสดง และเก็บข้อมูลเบื้องต้นหรือข้อมูลที่ทั่วไปไว้ใน
SharedPreferences ให้คงไว้ในเครื่อง จนกว่าจะล็อกเอาท์ซึ่งจะทำการลบออกเครืองไป สำหรับข้อมูล
ผู้ใช้เพิ่มเติม ที่เป็นข้อมูลสำคัญเราก็จะใช้การดึงข้อมูลผ่าน provider มาใช้งาน แนวทางประมาณนี้
ตัวอย่างลำดับการทำงาน
เนื้อหาที่ควรรู้เพิ่มเติม
เกี่ยวกับการใช้งาน http http://niik.in/1038
เกียวกับการใช้งาน provider http://niik.in/1046
เกี่ยวกับการใช้งาน SharedPreferences http://niik.in/1059
เกียวกับการใช้งาน form http://niik.in/1048
เริ่มต้นขั้นตอนการประยุกต์ดังนี้
สร้าง Data Model ของ user
เราสร้าง User model ให้สอดคล้องกับฐานข้อมูล จากบทความ REST API ระบบ Login
ได้ดังนี้ http://niik.in/1058
lib > models > user_model.dart
ไฟล์ user_model.dart
// กำหนดฟิลด์ข้อมูลของตาราง class UserFields { // สร้างเป็นลิสรายการสำหรับคอลัมน์ฟิลด์ static final List<String> values = [ id, email, name, token, createdate, lastlogin, active ]; // กำหนดแต่ละฟิลด์ของตาราง ต้องเป็น String ทั้งหมด static final String id = 'user_id'; static final String email = 'user_email'; static final String name = 'user_name'; static final String token = 'user_token'; static final String createdate = 'user_createdate'; static final String lastlogin = 'user_lastlogin'; static final String active = 'user_active'; } // ส่วนของ Data Model class User { final int id; final String email; final String? name; final String? token; final DateTime? createdate; final DateTime? lastlogin; final bool? active; // constructor const User({ required this.id, required this.email, this.name, this.token, this.createdate, this.lastlogin, this.active, }); // สำหรับแปลงข้อมูลจาก Json เป็น object static User fromJson(Map<String, Object?> json) => User( id: json[UserFields.id] as int, email: json[UserFields.email] as String, name: json[UserFields.name] as String, token: json[UserFields.token] as String, createdate: DateTime.parse(json[UserFields.createdate] as String), lastlogin: DateTime.parse(json[UserFields.lastlogin] as String), active: json[UserFields.active] == 1, ); // สำหรับแปลง object เป็น Json Map<String, Object?> toJson() => { UserFields.id: id, UserFields.email: email, UserFields.name: name, UserFields.token: token, UserFields.createdate: createdate?.toIso8601String() ?? "", UserFields.lastlogin: lastlogin?.toIso8601String() ?? "", UserFields.active: active!=null ? 1 : 0, }; }
ข้างต้นเรากำหนดให้แค่ id กับ email เป็นข้อมูลที่จำเป็นในการสร้าง User object ที่ต้องมี
ส่วนค่าอื่นๆ จะมีหรือไม่ก็ได้ไม่บังคับ
กำหนด API path ไฟล์
ให้เราสร้างไฟล์ สำหรับเก็บค่าคงที่ของ url ที่จะใช้งาน api ในที่นี้จะใช้เก็บไว้ใน
lib > constants > api_path.dart
ไฟล์ api_path.dart
class ApiUrl { // email และรหัสผ่านสำหรับทดสอบ test@gmail.com pass: demotest // ค่าตัวแปรสำหรับใช้งานจริง static const String liveBaseURL = "https://www.ninenik.com/demo/api/fake"; // กรณีทดสอบที่ localhost android ใช้ค่าตามนี้ได้เลย เปลี่ยน path และ port เท่านั้น static const String localBaseURL = "https://10.0.2.2:443/demo/api"; static const String baseURL = liveBaseURL; // ทดสอบที่เครื่องใช้ค่านี้ static const String login = "$baseURL/user/authen"; static const String register = "$baseURL/user/create"; }
กำหนดส่วนของ Provider
ต่อไปเป็นส่วนของการจัดการทั้งหมดของระบบสมาชิก เรารวมไว้ใน UserProvider class ในไฟล์
lib > utils > user_provider.dart
ไฟล์ user_provider.dart
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../constants/api_path.dart'; import '../models/user_model.dart'; class UserProvider with ChangeNotifier { // ใช้งานข้อมูล SharedPreferences final Future<SharedPreferences> _prefs = SharedPreferences.getInstance(); // ฟังก์ชั่นดึงสถานะการล็อกอิน จากข้อมูล SharedPreferences Future<bool> getLoginStatus() async { final SharedPreferences prefs = await _prefs; return prefs.getBool('loginSuccess') ?? false; } // ฟังก์ชั่นดึงข้อมูลผู้ใช้ทั่วไป จากข้อมูล SharedPreferences Future<User> getUser() async { final SharedPreferences prefs = await _prefs; return User( id: prefs.getInt('user_id')!, email: prefs.getString('user_email')!, token: prefs.getString('user_token')!, ); } // ฟังก์ชั่นดึงข้อมูล token จากข้อมูล SharedPreferences Future<String> getToken() async { final SharedPreferences prefs = await _prefs; String token = prefs.getString("user_token")!; return token; } // ฟังก์ชั่นล็อกเอาท์ออกจากระบบ ล้างค่าข้อมูล SharedPreferences Future<bool> logout() async { final SharedPreferences prefs = await _prefs; return await prefs.clear(); } // ฟังก์ชั่นดึงข้อมูลจาก server โดยใช้ token Future<Map<String, dynamic>> get(String token,int id) async { // final SharedPreferences prefs = await _prefs; var result; // ทำการดึงข้อมูลจาก server ตาม url ที่กำหนด final response = await http.get( Uri.parse(ApiUrl.baseURL+'/user/'+id.toString()), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer '+token }, ); // เมื่อมีข้อมูลกลับมา if (response.statusCode == 200) { var body = response.body; result = await json.decode(body); result = result[0]; // เนื่องจากข้อมูลจาก api เป็น array เลยใช้เฉพาะข้อมูล key = 0 notifyListeners(); } else { // กรณี error throw Exception('Failed to load data'); } return result; } // ฟังก์ชั่นสำหรับทำการล็อกอิน โดยส่งค่าไปยัง server Future<Map<String, dynamic>> authen(String email, String password) async { final SharedPreferences prefs = await _prefs; var result; final Map<String, dynamic> loginData = { 'email': email, 'password': password }; // ทำการดึงข้อมูลจาก server ตาม url ที่กำหนด final response = await http.post( Uri.parse(ApiUrl.login), body: json.encode(loginData), headers: {'Content-Type': 'application/json'}, ); // เมื่อมีข้อมูลกลับมา if (response.statusCode == 200) { var body = response.body; result = await json.decode(body); if(result['success']!=null){ // กรณีมีข้อมูลกลับบมา และล็อกอินผ่าน // บันทึกข้อมูลเบื้องต้นลงใน SharedPreferences await prefs.setInt("user_id", int.parse(result['id'])); await prefs.setString("user_email", result['email']); await prefs.setString("user_token", result['jwt']); await prefs.setInt("user_token_expired", result['expireAt']); await prefs.setBool("loginSuccess", true); notifyListeners(); } } else { // กรณี error throw Exception('Failed to load data'); } return result; } // ฟังก์ชั่นสำหรับสร้างบัญชีใหม่ โดยส่งค่าไปยัง server แล้วบันทึกลงฐานข้อมูล Future<Map<String, dynamic>> create(String email, String password) async { var result; // ข้อมูลผู้ใช้ที่ต้องส่งไป final Map<String, dynamic> userData = { 'email': email, 'password': password }; // ทำการดึงข้อมูลจาก server ตาม url ที่กำหนด final response = await http.post( Uri.parse(ApiUrl.register), // ใช้ url จากค่าที่กำหนด body: json.encode(userData), headers: {'Content-Type': 'application/json'}, ); // เมื่อมีข้อมูลกลับมา if (response.statusCode == 200) { var body = response.body; result = await json.decode(body); } else { // กรณี error throw Exception('Failed to load data'); } return result; } }
คำอธิบายเขียนไว้ในโค้ด
กำหนดใช้งาน Provider ใน app
ในไฟล์ main.dart จะของยกมาบางส่วนของโค้ด เฉพาะส่วนของการใช้งาน provider
ไฟล์ main.dart บางส่วน
...... .... return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => UserProvider()), ChangeNotifierProvider(create: (context) => Counter()), ], child: MaterialApp( theme: ThemeData( .... ...
การเรียกใช้งาน Provider
สุดท้ายเป็นการเรียกใช้งาน provider ในไฟล์ต่างๆ ตามลำดับ คำอธิบายแสดงในโค้ด
ไฟล์ profile.dart
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // import 'package:shared_preferences/shared_preferences.dart'; import 'login.dart'; import '../utils/user_provider.dart'; import '../models/user_model.dart'; import '../models/counter_model.dart'; class Profile extends StatefulWidget { static const routeName = '/profile'; const Profile({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _ProfileState(); } } class _ProfileState extends State<Profile> { late bool _loginSuccess; // กำหดตัวแปรสถานะการล็อกอิน // ส่วนของตัวแปรข้อมูลพื้นฐาน User? _user; int _id = 0; String _email = ''; String _token = ''; // ส่วนของตัวแปรสำหรับข้อมูลที่ดึงเพิ่มเติม String _createdate = ''; @override void initState() { super.initState(); loadSettings(); // เรียกใช้งานตั้งค่าเมื่อเริ่มต้นเป็นฟังก์ชั่น ให้รองรับ async } // ตั้งค่าเริ่มต้น void loadSettings() async { // ใช้งาน provider UserProvider userProvider = context.read<UserProvider>(); _loginSuccess = await userProvider.getLoginStatus(); // ถึงสถานะการล็อกอิน ถ้ามี if(_loginSuccess){ // ถ้าล็อกอินอยู่ fetchUser(); // ดึงข้อมูลของผู้ใช้ ถ้าล็อกอินอยู่ } } // ฟังก์ชั่นสำหรับดึงข้อมูลผู้ใช้ void fetchUser() async { // ใช้งาน provider UserProvider userProvider = context.read<UserProvider>(); setState(() { _loginSuccess = true; }); // ดึงข้อมูลทั่วไปของผู้ใช้ _user = await userProvider.getUser(); _email = _user!.email; _id = _user!.id; _token = _user!.token ?? ''; // ดึงข้อมูลเพิ่มเติมในฐานข้อมูลบน server Map<String, dynamic> _userExt = await userProvider.get(_token,_id); _createdate = _userExt['user_createdate']; } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { // ใช้งาน provider UserProvider userProvider = context.read<UserProvider>(); return Scaffold( appBar: AppBar( title: Text('Profile'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ FutureBuilder<bool>( future: userProvider.getLoginStatus(), // ข้อมูล Future builder: (context, snapshot) { if (snapshot.hasData) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Profile Screen'), Visibility( // ส่วนที่แสดงกรณีล็อกอินแล้ว visible: _loginSuccess, // ใช้สถานะการล็อกอินกำหนดกรแสดง child: Column( children: [ FlutterLogo(size: 100,), Text('Welcome member'), Text(_email), // แสดงอีเมล ElevatedButton( onPressed: () async { // เมื่อล็อกเอาท์ // ทำการออกจากระบบ await userProvider.logout(); setState(() { _loginSuccess = false; }); }, child: Text('Logout'), ), ], ), ), Visibility( // ส่วนที่แสดงกรณียังไม่ได้ล็อกอิน visible: !_loginSuccess, // ใช้สถานะตรงข้ามการล็อกอินกำหนดกรแสดง child: ElevatedButton( onPressed: () async { // กำหดให้รอค่า หลังจากเปิดไปหน้า lgoin final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => Login(), settings: RouteSettings( arguments: null ), ), ); // ถ้ามีการปิดหน้มที่เปิด และส่งค่ากลับมาเป็น true if (result == true) { setState(() { fetchUser(); // ดึงข้อมูลของผู้ใช้ ถ้าล็อกอินอยู่ _loginSuccess = true; }); } }, child: Text('Go to Login'), ), ), ], ) ); } else if (snapshot.hasError) { // ถ้ามี error return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }, ), Divider(), Text('You have pushed the button this many times:'), Count(), ], ) ), floatingActionButton: FloatingActionButton( key: const Key('increment_Button'), onPressed: () => context.read<Counter>().increment(), child: const Icon(Icons.add), ), ); } } class Count extends StatelessWidget { const Count({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text( '${context.watch<Counter>().count}', key: const Key('counterState'), style: Theme.of(context).textTheme.headlineMedium); } }
ไฟล์ login.dart
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../utils/user_provider.dart'; import 'register.dart'; class Login extends StatefulWidget { static const routeName = '/login'; const Login({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _LoginState(); } } class _LoginState extends State<Login> { // สร้างฟอร์ม key หรือ id ของฟอร์มสำหรับอ้างอิง final _formKey = GlobalKey<FormState>(); // กำหนดตัวแปรรับค่า final _email = TextEditingController(); final _password = TextEditingController(); // กำหนดสถานะการแสดงแบบรหัสผ่าน bool _isHidden = true; bool _authenticatingStatus = false; @override void initState() { super.initState(); } @override void dispose() { _email.dispose(); // ยกเลิกการใช้งานที่เกี่ยวข้องทั้งหมดถ้ามี _password.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // ใช้งาน provider UserProvider userProvider = context.read<UserProvider>(); return Scaffold( appBar: AppBar( title: Text('Login'), ), body: SingleChildScrollView( child: Form( key: _formKey, // กำหนด key child: Padding( padding: const EdgeInsets.all(15.0), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ SizedBox(height: 20.0,), FlutterLogo( size: 100, ), Text('Login Screen'), TextFormField( decoration: InputDecoration( hintText: 'Email', icon: Icon(Icons.email_outlined), ), controller: _email, // ผูกกับ TextFormField ที่จะใช้ ), SizedBox(height: 5.0,), TextFormField( decoration: InputDecoration( hintText: 'Password', icon: Icon(Icons.vpn_key), suffixIcon: IconButton( onPressed: (){ setState(() { _isHidden = !_isHidden; // เมื่อกดก็เปลี่ยนค่าตรงกันข้าม }); }, icon: Icon( _isHidden // เงื่อนไขการสลับ icon ? Icons.visibility_off : Icons.visibility ), ), ), controller: _password, // ผูกกับ TextFormField ที่จะใช้ obscureText: _isHidden, // ก่อนซ่อนหรือแสดงข้อความในรูปแบบรหัสผ่าน ), SizedBox(height: 10.0,), Visibility( visible: !_authenticatingStatus, child: ElevatedButton( onPressed: () async { // เปลี่ยนสถานะเป็นกำลังล็อกอิน setState(() { _authenticatingStatus = !_authenticatingStatus; }); // อ้างอิงฟอร์มที่กำลังใช้งาน ตรวจสอบความถูกต้องข้อมูลในฟอร์ม if (_formKey.currentState!.validate()) { //หากผ่าน FocusScope.of(context).unfocus(); // ยกเลิดโฟกัส ให้แป้นพิมพ์ซ่อนไป String email = _email.text; String password = _password.text; // ใช้ provider ส่ง request ล็อกอินไปยัง server var result = await userProvider.authen(email, password); // จำลองเปรียบเทียบค่า เพื่อทำการล็อกอิน if(result['success']!=null){ // ล็อกอินผ่าน ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Login Successful')), ); Navigator.pop(context, true); // ปิดหน้านี้พร้อมคืนค่า true }else{ if(result['error']!=null){ // ล็อกอินไม่ผ่านมี error String error = result['error']; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('${error}.. try agin!')), ); setState(() { _authenticatingStatus = !_authenticatingStatus; }); }else{ // ล็อกอินไม่ผ่าน อื่นๆ ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error.. try agin!')), ); setState(() { _authenticatingStatus = !_authenticatingStatus; }); } } } }, child: Container( alignment: Alignment.center, width: double.infinity, child: const Text('Login'), ), ), ), Visibility( visible: _authenticatingStatus, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ CircularProgressIndicator(), SizedBox(width: 10.0,), Text(" Authenticating ... Please wait") ], ), ), SizedBox(height: 30.0,), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Or '), InkWell( child: Text('Register', style: TextStyle( decoration: TextDecoration.underline, color: Colors.blue )), onTap: () async { // เปิดหน้า สมัครสมาชิก โดย ซ้อนหน้า ล็อกอินเดิม ใช้ push แทน // ไม่เช่นนั้นจะไม่มีค่าส่งกลับจากหน้าล็อกอิน ที่เรารออรับค่าอยู่ // Navigator.pushReplacement( Navigator.push( context, MaterialPageRoute(builder: (context) => Register(), settings: RouteSettings( arguments: null ), ), ); }, ) ], ) ], ) ), ), ), ), ); } }
ไฟล์ register.dart
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../utils/user_provider.dart'; // import 'login.dart'; class Register extends StatefulWidget { static const routeName = '/register'; const Register({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _RegisterState(); } } class _RegisterState extends State<Register> { // สร้างฟอร์ม key หรือ id ของฟอร์มสำหรับอ้างอิง final _formKey = GlobalKey<FormState>(); // กำหนดตัวแปรรับค่า final _email = TextEditingController(); final _password = TextEditingController(); // กำหนดสถานะการแสดงแบบรหัสผ่าน bool _isHidden = true; bool _registeringStatus = false; @override void initState() { super.initState(); } @override void dispose() { _email.dispose(); // ยกเลิกการใช้งานที่เกี่ยวข้องทั้งหมดถ้ามี _password.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // ใช้งาน provider UserProvider userProvider = context.read<UserProvider>(); return Scaffold( appBar: AppBar( title: Text('Register'), ), body: SingleChildScrollView( child: Form( key: _formKey, // กำหนด key child: Padding( padding: const EdgeInsets.all(15.0), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ SizedBox(height: 20.0,), FlutterLogo( size: 100, ), Text('Register Screen'), TextFormField( decoration: InputDecoration( hintText: 'Email', icon: Icon(Icons.email_outlined), ), controller: _email, // ผูกกับ TextFormField ที่จะใช้ ), SizedBox(height: 5.0,), TextFormField( decoration: InputDecoration( hintText: 'Password', icon: Icon(Icons.vpn_key), suffixIcon: IconButton( onPressed: (){ setState(() { _isHidden = !_isHidden; // เมื่อกดก็เปลี่ยนค่าตรงกันข้าม }); }, icon: Icon( _isHidden // เงื่อนไขการสลับ icon ? Icons.visibility_off : Icons.visibility ), ), ), controller: _password, // ผูกกับ TextFormField ที่จะใช้ obscureText: _isHidden, // ก่อนซ่อนหรือแสดงข้อความในรูปแบบรหัสผ่าน ), SizedBox(height: 10.0,), Visibility( visible: !_registeringStatus, child: ElevatedButton( onPressed: () async { // เปลี่ยนสถานะกำลังสมัครสมาชิก setState(() { _registeringStatus = !_registeringStatus; }); if (_formKey.currentState!.validate()) { //หากผ่าน FocusScope.of(context).unfocus(); // ยกเลิดโฟกัส ให้แป้นพิมพ์ซ่อนไป String email = _email.text; String password = _password.text; // ใช้ provider ส่ง request สร้างบัญชีสมาชิกใหม่ var result = await userProvider.create(email, password); if(result['success']!=null){ // สร้างบัญชีสำเร็จ ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Create new user Successful')), ); // กลับไปยังหน้าล็อกอิน โดยปิดหน้านี้จากที่กำลังซ้อนอยู่ Navigator.pop(context); // ปิดหน้านี้ }else{ if(result['error']!=null){ // สร้างบัญชีไม่ผ่าน String error = result['error']; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('${error}.. try agin!')), ); setState(() { _registeringStatus = !_registeringStatus; }); }else{ // สร้างบัญชีไม่ผ่าน อื่นๆ ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error.. try agin!')), ); setState(() { _registeringStatus = !_registeringStatus; }); } } } }, child: Container( alignment: Alignment.center, width: double.infinity, child: const Text('Register'), ), ), ), Visibility( visible: _registeringStatus, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ CircularProgressIndicator(), SizedBox(width: 10.0,), Text(" Registering ... Please wait") ], ), ), SizedBox(height: 30.0,), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Already member? '), InkWell( child: Text('Login', style: TextStyle( decoration: TextDecoration.underline, color: Colors.blue )), onTap: (){ // กลับไปยังหน้าล็อกอิน โดยปิดหน้านี้จากที่กำลังซ้อนอยู่ Navigator.pop(context); // ปิดหน้านี้ // เราไม่ใช้การเปลี่ยนหน้าด้วย pushReplacement เพื่อให้หน้า login ส่งค่ากลับได้ /* Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => Login(), settings: RouteSettings( arguments: null ), ), ); */ }, ) ], ) ], ) ), ), ), ), ); } }
เนื้อหาบทความนี้จะเน้นที่ตัวอย่าง และคำอธิบายในโค้ดเท่านั้น เป็นแนวทางทำความเข้าใจ หรือ
นำไปปรับประยุกต์ตามต้องการ หลักการทำงานก็คล้ายๆจากบทความตอนที่แล้ว เพียงแต่เนื้อหานี้
เราประยุกต์ใช้งานจริง
ตัวอย่างลำดับการทำงาน
เมื่อเรามายังหน้าสมัครสมาชิก ลองกรอกข้อมูลเฉพาะอีเมล แล้วทดสอบสมัครสมาชิก
ก็จะไม่ผ่าน และขึ้นข้อความแจ้งว่าต้องกรอกทั้งอีเมลและรหัสผ่าน ตามรูป
ต่อไปเราลองกรอกข้อมูลตามตัวอย่าง โดยให้แสดงรหัสผ่านให้เห็น จะได้ขั้นตอนขณะ
กำลังสมัคร ดังนี้
เมื่อสมัครสมาชิก สำเร็จ ก็จะมายังหน้าล็อกอิน
ตอนนี้ในฐานข้อมูลบน server ของเรามีข้อมูลที่ถูกเพิ่มเข้าไปแล้ว
ในหน้าล็อกอินเราจะลองทดสอบ error ต่างๆ เช่น ไม่กรอกข้อมูลใดๆ กรอกเฉพาะอีเมล กรอก
รูปแบบอีเมลไม่ถูกต้อง ก็จะขึ้น error ต่างๆ ตามที่กำหนดใน API
ต่อไปกรอกข้อมูลสมาชิกที่เราเพิ่งสมัครไป ให้ถูกต้อง ก็จะล็อกอินผ่าน และแสดงข้อมูล
สมาชิกดังตัวอย่าง
เกี่ยวกับการจัดการข้อมูลสมาชิกบน server ของเราเองก็มีเนื้อหาประมาณนี้ หวังว่าจะเป็นแนวทาง
ปรับใช้งานต่อไป เนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม