เนื้อหาต่อไปนี้ เราจะมาใช้เทคนิคอย่างง่าย ในการทำระบบ
ล็อกแอป หรือระบบความปลอดภัยส่วนตัวแบบ Local Authen
โดยใช้งานในรูปแบบ PIN number ที่ให้เราสามารถกำหนดให้แอป
สามารถเข้าใช้งานได้เมื่อใส่รหัส PIN ที่ถูกต้อง ซึ่งเราจะต้องทำการเปิด
การใช้งานการตั้งค่า PIN ในครั้งแรก หลังจากนั้่น หากมีการเปิดเข้าใช้งาน
แอปอีกครั้ง ก็จะต้องกรอกรหัส PIN ให้ถูกต้อง ถึงจะเข้าใช้งานได้ อาจจะเหมาะ
กับกรณีแอปของเรามีการเก็บข้อมูลที่บันทึกไว้ และต้องการให้เพียงเราเท่านั้น
ที่สามารถเข้าดูและใช้งานได้ แบบนี้เป็นต้น
เนื้อหานี้ใช้ไฟล์ตัวอย่างจากบทความ ไฟล์เนื้อหาเพิ่มเติมที่ 2
การใช้งาน BottomNavigationBar ใน Flutter เบื้องต้น http://niik.in/961
แนวทางและการเรียนรู้จากบทความนี้
- การสร้างการกำหนดการเปิดใช้งาน PIN
- การสร้างหน้ากรอก PIN และการตรวจสอบเงื่อนไขการเข้าใช้งาน
- การใช้งาน shared preferences เพื่อเก็บค่า PIN และเรียกใช้งาน
ค่าตัวแปรที่เกี่ยวข้องและการอธิบาย
- pincodeStatus ค่า bool เก็บสถานะเปิดใช้งาน pin หรือไม่
- pincodeValue ค่า String เก็บข้อมูลเลข PIN ที่บันทึกไว้ตรวจสอบ
- authorized ค่า bool เก็บสถานะอนุญาตเข้าใช้งาน ถ้าว่ากรอกรหัส PIN ถูกต้อง
ลำดับขั้นตอนการทำงาน
- เมื่อเข้าใช้งานแอป ตัวแอปจะอ่านค่า pincodeStatus เพื่อดูสถานะว่ามีการล็อกแอปไหม
- ถ้ามีการล็อกแอปด้วย pin ก็จะเรียกหน้ากรอก pin ขึ้นมาแสดง
- เมื่อผู้ใช้กรอก pin ถูกต้องก็เข้าใช้งานแอปได้
- ถ้าอ่านค่า pincodeStatus ยังไม่มีการล็อกแอป ก็เข้าใช้งานปกติ แต่มีหน้า settings
ที่สามารถเข้าไปเปิดการตั้งค่าการล็อกแอปได้ เมื่อเปิดการตั้งค่า ก็จะขึ้นหนัา pin มาให้กำหนด
ค่า เพื่อตั้งค่า pin ไว้ใช้งาน และเก็บค่าไว้ใน pincodeValue ไว้ตรวจสอบ
จากนั้นก็กำหนดค่า authorized เป็น true เพื่อให้สามารถเข้าใช้งานหลังจากตั้งค่าแล้ว
และเมื่อปิดและเปิดแอปมาอีกครั้งก็จะขึ้นหน้า pin ให้กรอก แบบนี้เป็นตัน
เนื้อหานี้จำเป็นต้องรู้จักการใช้งาน shared preferences สำหรับเก็บข้อมูล ดูได้ที่
ประยุกต์เก็บข้อมูลด้วย shared preferences ใน Flutter http://niik.in/1059
สร้างหน้าเปิดปิดการตั้งค่า PIN number
หลังจากทำความเข้าใจแนวทางไปแล้ว มาลงรายละเอียดที่โค้ดกัน ไฟล์หลักของเราจะมี 2-3 ไฟล์ คือ
ไฟล์ สำหรับใช้ตั้งค่าการเปิดปิด PIN ใช้เป็นไฟล์ settings.dart และไฟล์ที่จัดการเกี่ยวกับ PIN
ทั้งหมด ไม่ว่าจะเป็นการตั้งค่าเมื่อเปิดใช้ การยกเลิกการตั้ง และการตรวจสอบ PIN ก่อนใช้งาน ทั้งสาม
ส่วนนี้เราจะไว้ในไฟล์เดียวคือไฟล์ pincode.dart
*คำอธิบายแสดงในโค้ด
ไฟล์ settings.dart
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'pincode.dart'; class Settings extends StatefulWidget { static const routeName = '/settings'; const Settings({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _SettingsState(); } } class _SettingsState extends State<Settings> { late final SharedPreferences prefs; bool _pincodestatus = false; // สถานะเปิดใช้ pin bool _authorized = false; // สถานะเข้าใช้งาน String _pincodevalue = ''; // ค่า pin ในที่นี้ใช้ 4 ตัวเลข bool _isLoadingPrefs = true; // สถานะการโหลดค่าจาก SharedPreferences @override void initState() { super.initState(); // โหลดค่าจาก SharedPreferences _loadValueFromSharedPreferences(); } // Method to load value from SharedPreferences Future<void> _loadValueFromSharedPreferences() async { prefs = await SharedPreferences.getInstance(); setState(() { _pincodestatus = prefs.getBool('pincodestatus') ?? _pincodestatus; _authorized = prefs.getBool('authorized') ?? _authorized; _pincodevalue = prefs.getString('pincodevalue') ?? _pincodevalue; _isLoadingPrefs = false; // โหลดค่าเรียบร้อยแล้ว }); } @override Widget build(BuildContext context) { if (_isLoadingPrefs) { // คืนค่ากรณี กำลังโหลดค่า SharedPreferences ยังไม่เสร็จ return const Center(child: SizedBox.shrink()); } print("debug: _pincodestatus ${_pincodestatus}"); print("debug: _authorized ${_authorized}"); print("debug: _pincodevalue ${_pincodevalue}"); return Scaffold( appBar: AppBar( title: Text('Settings'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ // Text('Settings Screen'), ListTile( title: Text('Lock app with PIN number'), trailing: Checkbox( value: _pincodestatus, onChanged: (bool? value) async { // เมื่อมีการเปลี่ยนแปลงการตั้งค่า // เปิดหน้ากำหนด pin และรอดำเนินการจากหน้านั้น final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => Pincode()), ); // จัดการเงื่อนไขรับค่าที่ส่งกลับมา print("debug: ${result}"); if (result == 'cancel') { // ถ้าเป็นการยกเลิก setState(() { _authorized = false; _pincodestatus = false; _pincodevalue = ''; }); } else { // ถ้ามีการตั้งค่า result ที่ส่งกลับมาเป็นค่า code if (result != null) { setState(() { _authorized = true; _pincodestatus = true; _pincodevalue = result; }); } } }, ), ), ], )), ); } }
ไฟล์ pincode.dart
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'launcher.dart'; class Pincode extends StatefulWidget { static const routeName = '/pincode'; const Pincode({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _PincodeState(); } } class _PincodeState extends State<Pincode> { late final SharedPreferences prefs; bool _pincodestatus = false; // สถานะเปิดใช้ pin bool _authorized = false; // สถานะเข้าใช้งาน String _pincodevalue = ''; // ค่า pin ในที่นี้ใช้ 4 ตัวเลข bool _isLoadingPrefs = true; // สถานะการโหลดค่าจาก SharedPreferences // วนลูปสร้าง controller และ focusNodes ตามจำนวนที่ต้องการ // ในที่นี้ใช้แค่ 4 ตัว สามารถปรับเป็นจำนวนที่ต้องการได้ final _codeController = List.generate(4, (index) => TextEditingController()); final _focusNodes = List.generate(4, (index) => FocusNode()); @override void initState() { super.initState(); // Autofocus ไฟที่ช่องตัวเลขแรก _focusNodes[0].requestFocus(); // โหลดค่าจาก SharedPreferences _loadValueFromSharedPreferences(); } // โหลดข้อมูลจาก SharedPreferences Future<void> _loadValueFromSharedPreferences() async { prefs = await SharedPreferences.getInstance(); setState(() { _pincodestatus = prefs.getBool('pincodestatus') ?? _pincodestatus; _authorized = prefs.getBool('authorized') ?? _authorized; _pincodevalue = prefs.getString('pincodevalue') ?? _pincodevalue; _isLoadingPrefs = false; // Set to false once the data is loaded }); } // ฟังก์์ชั่นสำหรับยกเลิกการกำหนด pin void _verifyCodeToCancel() async { // รวมตัวเลขแต่ละช่องเป็นรหัส pin 4 ตัว String code = _codeController.map((controller) => controller.text).join(); // ถ้าจะยกเลิก ต้องกรอกรหัส pin เดิมให้ถูกต้อง if (code == _pincodevalue) { await prefs.setString("pincodevalue", ''); await prefs.setBool("pincodestatus", false); await prefs.setBool("authorized", false); showDialog( context: context, builder: (context) => AlertDialog( title: Text("Success"), content: Text("Pin verified successfully!"), actions: [ TextButton( onPressed: () { // มีการซ้อนทับ 2 ชั้น ต้องเอาหน้าเพจชิ้นบนออก สองครั้ง // คือ showDialog และหน้า pin Navigator.of(context).pop(); // เมื่อเอาหน้า pin ออกหลังตรวจสอบสำเร็จ ส่งค่า cancel กลับไปหน้าหลัก Navigator.pop(context, 'cancel'); }, child: Text("OK"), ) ], ), ); } else { // ถ้ากรอกผิด ขึ้นให้ลองใหม่ จนกว่าจะกรอกถูก หรือยกเลิกไป showDialog( context: context, builder: (context) => AlertDialog( title: Text("Error"), content: Text("Incorrect pin. Try again."), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text("OK"), ) ], ), ); } } // ฟังก์ชั่นตรวจสอบค่า pin ที่กรอกเพือเข้าใช้งาน void _verifyCode() async { String code = _codeController.map((controller) => controller.text).join(); if (code == _pincodevalue) { // ถ้ากรอกข้อมูล pin ถูกต้อง showDialog( context: context, builder: (context) => AlertDialog( title: Text("Success"), content: Text("Pin verified successfully!"), actions: [ TextButton( onPressed: () async { // เก็บสถานะการเข้าใช้งาน เป็น true แล้ว แทนหน้า pin ปัจจุบ้น ด้วย /// หน้าหลักของแอป ในที่นี้คือหน้า Launcher // เนื่องจากมีการซ้อนทับ 2 ชั้น ต้องเอาหน้าเพจชิ้นบนออก สองครั้ง // คือ showDialog และหน้า pin await prefs.setBool("authorized", true); Navigator.of(context).pop(); Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const Launcher()), ); }, child: Text("OK"), ) ], ), ); } else { // ถ้ายังกรอกไม่ถูกต้อง ก็ไม่สามารถเข้าใช้งานได้ ต้องกรอกจนกว่าจะถูกต้อง showDialog( context: context, builder: (context) => AlertDialog( title: Text("Error"), content: Text("Incorrect pin. Try again."), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text("OK"), ) ], ), ); } } // ฟังก์ชั่นตรวจสอบสำหรับกยเลิกการเปิดใช้งาน pin void _checkAutoVerify() { String code = _codeController.map((controller) => controller.text).join(); if (code.length == 4 && !_codeController.any((controller) => controller.text.isEmpty)) { _verifyCodeToCancel(); } } // ฟังก์ชั่นตั้งค่า pin เมื่อเปิดใช้งานการใช้งาน pin void _setAutoVerify() async { String code = _codeController.map((controller) => controller.text).join(); if (code.length == 4 && !_codeController.any((controller) => controller.text.isEmpty)) { await prefs.setString("pincodevalue", code); await prefs.setBool("pincodestatus", true); // เมื่อตั้งค่าแล้ว ส่งค่า code กลับออกไป พร้อมปิดหน้า dialog Navigator.pop(context, code); } } @override void dispose() { // ล้างค่าตัวแปรที่ไมได้ใช้งาน _codeController.forEach((controller) => controller.dispose()); _focusNodes.forEach((focusNode) => focusNode.dispose()); super.dispose(); } @override Widget build(BuildContext context) { if (_isLoadingPrefs) { // คืนค่ากรณี กำลังโหลดค่า SharedPreferences ยังไม่เสร็จ return const Center(child: SizedBox.shrink()); } print("debug: _pincodestatus ${_pincodestatus}"); print("debug: _authorized ${_authorized}"); print("debug: _pincodevalue ${_pincodevalue}"); // รับค่า arguments ที่ส่งมาใช้งาน จากการเข้าใช้งานแอปครั้งแรก และมีการเปิดใช้งาน pin final args = ModalRoute.of(context)!.settings.arguments as String?; print("debug: args ${args}"); return Scaffold( appBar: AppBar( title: Text('PinCode'), ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Enter your PIN number', style: TextStyle(fontSize: 20), textAlign: TextAlign.center, ), SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(4, (index) { return Container( width: 50, child: TextField( controller: _codeController[index], focusNode: _focusNodes[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, maxLength: 1, autofocus: index == 0, // obscureText: true, // ซ่อนข้อมูลแสดงแบบรหัสผ่าน decoration: InputDecoration( counterText: '', filled: true, // Enables background color fillColor: Colors.grey.shade200, // Set the background color border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide( color: Colors.black, // Border color width: 2.0, // Border thickness ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide( color: Colors.blue, // Border color when focused width: 2.0, // Border thickness ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide( color: Colors.grey .withAlpha(50), // Border color when not focused width: 2.0, ), ), ), onChanged: (value) { // ส่วนการจัดการการโฟกัส ช่องกรอกข้อมูลอัตโนมัติ if (value.length == 1 && index < 3) { FocusScope.of(context) .requestFocus(_focusNodes[index + 1]); } else if (value.isEmpty && index > 0) { FocusScope.of(context) .requestFocus(_focusNodes[index - 1]); } // ตรวจสอบข้อมูล pin ที่กรอกเข้ามา String code = _codeController .map((controller) => controller.text) .join(); // ถ้าเป็น pin ที่มี 4 ตัว และไม่ใช่ค่าว่าง if (code.length == 4 && !_codeController .any((controller) => controller.text.isEmpty)) { if (args != null) { // ถ้าส่งมาจากหน้าแรกหรือ launcher _verifyCode(); // ตรวจสอบการเข้าใช้งานด้วย pin } else { // ถ้าเปิดใช้งานอยู่ ต้องการปิดใช้งาน if (_pincodestatus) { _checkAutoVerify(); } else { // ต้องการตั้งค่า pin เพื่อใช้งาน _setAutoVerify(); } } } }, ), ); }), ), SizedBox(height: 16), TextButton( onPressed: () { // Handle resend code logic }, child: Text( args != null // ถ้าสงมาจากหน้าแรก ? 'ใส่รหัส PIN เพื่อเข้าใช้งาน' : _pincodestatus // ถ้าเป็นการตั้งค่า ? 'ใส่รหัส PIN เพื่อยกเลิก' : 'ใส่รหัส PIN เพื่อยเปิดใช้งานล็อกแอป', style: TextStyle(color: Colors.black), ), ), ], ), ), ); } }
ตัวอย่างผลลัพธ์การทำงาน
กรณีเปิดการใช้งาน
กรณียกเลิกการใช้งาน
ครั้งแรก เมื่อเราเข้าใช้งาน และมายังหน้า settings ก็จะมีให้เลือกว่าจะเปิดใช้งาน pin หรือไม่ ถ้าเรา
เลือกเปิดใช้งาน ก็จะแสดงหน้า pincode ให้เรากำหนด pin เพื่อใช้งาน เมื่อกดตั้งค่า pin เรียบร้อยแล้ว
สถานะการเปิดใช้งาน pin ก็จะเป็น true
และเมื่อสมมติเราต้องการยกเลิกการตั้งค่า pin ก็ให้กดที่การตั้งค่าอีกครั้ง หน้า pincode ก็จะแสดงให้
เรากรอก pin เพื่อยืนยันการยกเลิก เป็นอันเสร็จเรียบร้อยตามกระบวนการ
การเรียกใช้งานตรวจสอบ PIN เมื่อเข้าใช้งาน
โค้ดแอปตัวอย่างของเราจะมีไฟล์หน้าเริ่มต้นคือ launcher.dart ดังนั้นเราจะแก้ไขในไฟล์ส่วนนี้
แนวทางการทำงาน เมื่อเราได้ทำการเปิดใช้งาน pin เพื่อล็อกแอปแล้ว ถ้าเข้ามาครั้งแรกที่หน้า
launcher ก็จะตรวจสอบค่าที่บันทึกใน SharedPreferences ว่ามีการเปิดใช้งาน pin และยังไม่ได้
ปลอดล็อกเพื่อเข้าใช้งานหรือไม่ ถ้ายังเปิดใช้งาน pin แต่ยังไม่ปลดล็อก ก็จะทำการดึงหน้า pincode
มาแสดงแทนหน้า launcher โดยในการดึงมาแสดง เราก็มีการส่งค่าข้อมูลไปยังไฟล์ pincode.dart
เพื่อใช้เป็นเงื่อนไขการเช้าใช้งาน
หากผู้ใช้กรอก pin ไม่ถูกต้องก็จะไม่สามารถเข้าใช้งานแอปได้จะค้างที่หน้า pin แต่ถ้าผู้ใช้กรอก
ข้อมูล pin ถูกต้องตามที่ได้กำหนดไว้ ก็จะทำการบันทึกสถานะต่างๆ ไว้ใน SharedPreferences
ให้สามารถเรียกใช้ภายหลังอีกได้
อย่างไรก็ดีในการตั้งค่าเมื่อใช้งาน pin จะมีจุดหนึ่งที่เราต้องสนใจคือ เมื่อเราเข้าใช้งานสำเร็จผ่าน
pin แล้่ว ค่าการใช้งานจะถูกบันทึกเป็น true ดังนั้น ที่เราต้องการคือ ต้องมีการกรอก pin ทุกครั้ง
ที่เปิดแอปขึ้นมาใหม่หรือมีการหยุดแล้วกลับมาใหม่ ซึ่งในกระบวนการนี้ เราจะต้องกำหนดการทำงาน
ให้กับ AppLifecycleState เพิ่มเข้ามา และมีการกำหนดให้ลบหรือล้างค่าการปลดล็อก เมื่ออยู่ใน
สถานะดังกล่าว คำอธิบายแสดงในโค้ด
ไฟล์ launcher.dart
import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'home.dart'; import 'contact.dart'; import 'profile.dart'; import 'about.dart'; import 'settings.dart'; import 'pincode.dart'; import '../components/sidemenu.dart'; class Launcher extends StatefulWidget { static const routeName = '/'; const Launcher({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _LauncherState(); } } class _LauncherState extends State<Launcher> with WidgetsBindingObserver { late final SharedPreferences prefs; bool _pincodestatus = false; // สถานะเปิดใช้ pin bool _authorized = false; // สถานะเข้าใช้งาน String _pincodevalue = ''; // ค่า pin ในที่นี้ใช้ 4 ตัวเลข bool _isLoadingPrefs = true; // สถานะการโหลดค่าจาก SharedPreferences int _selectedIndex = 0; final List<Widget> _pageWidget = <Widget>[ const Home(), const About(), const Profile(), const Contact(), const Settings(), ]; final List<BottomNavigationBarItem> _menuBar = <BottomNavigationBarItem>[ const BottomNavigationBarItem( icon: Icon(FontAwesomeIcons.house), label: 'Home', ), const BottomNavigationBarItem( icon: Icon(FontAwesomeIcons.circleInfo), label: 'About', ), const BottomNavigationBarItem( icon: Icon(FontAwesomeIcons.userLarge), label: 'Profile', ), const BottomNavigationBarItem( icon: Icon(FontAwesomeIcons.addressCard), label: 'Contact', ), const BottomNavigationBarItem( icon: Icon(FontAwesomeIcons.gear), label: 'Settings', ), ]; @override void initState() { super.initState(); // โหลดค่าจาก SharedPreferences _loadValueFromSharedPreferences(); // กำหนดการตรวจจับ สถานะ state ของ app WidgetsBinding.instance.addObserver(this); } @override void dispose() { // ล้างค่าตรวจจับ สถานะ state ของ app WidgetsBinding.instance.removeObserver(this); super.dispose(); } // ส่วนของการทำงานเมื่อ state ของ app มีการเปลี่ยนแปลง // บาง ค่าของ LifecycleState อาจจะไม่ทำงานได้ @override void didChangeAppLifecycleState(AppLifecycleState state) async { print("debug: AppLifecycleState: $state"); if (state == AppLifecycleState.paused) { // ล้างค่าการปลดล็อก ถ้ามีการหยุดชั่วคราวของแอป _authorized = false; await prefs.setBool('authorized', false); print("debug: App is in background."); } else if (state == AppLifecycleState.resumed) { print("debug: App is in foreground."); } else if (state == AppLifecycleState.detached) { // ล้างค่าการปลดล็อก ถ้ามีการปิดแอป ส่วนนี้อาจจะไทม่ทำงานได้ // และบางครั้ง เวลาเริ่มแอปใหม่ อาจจะไม่มีการใส่ pin ได้ _authorized = false; await prefs.setBool('authorized', false); print("debug: App is about to be terminated."); } } // โหลดข้อมูลจาก SharedPreferences Future<void> _loadValueFromSharedPreferences() async { prefs = await SharedPreferences.getInstance(); setState(() { _pincodestatus = prefs.getBool('pincodestatus') ?? _pincodestatus; _authorized = prefs.getBool('authorized') ?? _authorized; _pincodevalue = prefs.getString('pincodevalue') ?? _pincodevalue; _isLoadingPrefs = false; // โหลดค่าเรียบร้อยแล้ว // เงื่่อนไขถ้ามีการเปิดใช้งานการล็อกแอป และยังไม่ปลดล็อก if (_pincodestatus && !_authorized) { _authen(); // เรียกใช้งานการตรวจสอบ pin } }); } void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); } // การตรวจสอบ pin โดยแทนที่หน้าหลักด้วยหน้า pincode void _authen() async { String? args = 'pin'; // ส่งค่านี้ไป เพื่อใช้แยกว่าเป็นการ ตรวจสอบเข้าใช้งาน await Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => Pincode(), settings: RouteSettings(arguments: args // ส่งค่าไปใน arguments ), ), ); } @override Widget build(BuildContext context) { if (_isLoadingPrefs) { // คืนค่ากรณี กำลังโหลดค่า SharedPreferences ยังไม่เสร็จ return const Center(child: SizedBox.shrink()); } print("debug: _pincodestatus ${_pincodestatus}"); print("debug: _authorized ${_authorized}"); print("debug: _pincodevalue ${_pincodevalue}"); return Scaffold( body: _pageWidget.elementAt(_selectedIndex), bottomNavigationBar: BottomNavigationBar( items: _menuBar, currentIndex: _selectedIndex, selectedItemColor: Theme.of(context).primaryColor, unselectedItemColor: Colors.grey, onTap: _onItemTapped, ), drawer: SideMenu(), ); } }
ผลลัพธ์การทำงาน
เมื่อเปิดใช้งานการล็อกแอป หากเข้ามาใช้งานครั้งแรก ก็จะเปิดหน้า pin ให้เรากรอกรหัส pin ตามที่
ได้ตั้งไว้ให้ถูกต้อง ถ้ากรอกผิด ก็จะขึ้นแจ้งเตือนให้กรอก จนกว่าจะกรอกถูก ไม่เช่นนั้นก็จะเข้าใช้งานไม่ได้
เมื่อกรอกถูกต้อง ระบบก็จะโหลดหน้าหลัก launcher มาแสดง
ข้อสังเกตในโค้ดคือ จะมีการใช้งาน WidgetsBindingObserver Mixin ในส่วนของ State class
class _LauncherState extends State<Launcher> with WidgetsBindingObserver {
เพื่อใช้ในการตรวจสอบและจัดการเกี่ยวกับ AppLifecycleState ตามที่ได้อธิบายการทำงานในโค้ด
เนื้อหานี้เป็นนวทางการจัดการเกี่ยวกับ local authen ที่เรากำหนดและจัดรุปแบบเอง สามารถ
นำไปปรับประยุกต์เพิ่มเติมได้ และเนื้อหานี้ ก็จะต่อยอดไปถึงตอนหน้า เกี่ยวกับ local authen เกี่ยวกับ
การปลดล็อกด้วยระบบของเครื่องเช่น pattern หรือ ใบหน้า หรือ สแกนลายนิ้วมือ รอติดตาม