ประยุกต์เก็บข้อมูลด้วย shared preferences ใน Flutter

เขียนเมื่อ 2 ปีก่อน โดย Ninenik Narkdee
shared preferences flutter

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ shared preferences flutter

ดูแล้ว 7,790 ครั้ง




เนื้อหาตอนต่อไปนี้ เราจะมาประยุกต์การใช้งาน plugin ที่ใช้
สำหรับเก็บข้อมูลในรูปแบบ key-value ไว้ในเครื่อง โดยเรา
จะจำลองระบบสมาชิก เก็บข้อมูลเท่าที่จำเป็นของสมาชิกที่กำลัง
ล็อกอินใช้งาน app อยู่ ในเนื้อหาจะเป็นรูปแบบจำลองการสมัคร
สมาชิก การล็อกอิน และการเข้าใช้งานเสมือนเป็นระบบสมาชิกหนึ่งๆ
เพียงแต่ว่า ข้อมูลจะเป็นลักษณะกำหนดแบบตายตัว เพื่อจำลอง
การทำงานเท่านั้น อย่างไรก็ตามเนื้อหานี้ก็เป็นแนวทาง สำหรับเนื้อหา
การประยุกต์ต่อไปด้วย
 
   *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/1050
 
 

สิ่งที่เราจะจำลองการทำงาน

    เราจะจำลองเสมือนว่าใน app ของเรามีบางส่วนของข้อมูล ที่ใช้งานเฉพาะสมาชิกเท่านั้น
ในที่นี้ก็คือส่วนของ profile เมื่้อยังไม่ได้เข้าสู่ระบบหรือยังไม่ได้เป็นสมาชิก ก็มีลิ้งค์ให้ผู้ใช้เลือก
ทำการล็อกอินหรือสมัครสมาชิก และเมื่อผู้ใช้สมัครสมาชิกและทำการล็อกอินเข้าใช้งานแล้ว ก็ให้
แสดงเนื้อหาส่วนของสมาชิกในหน้า profile ผู้ใช้สามารถล็อกเอาท์หรืออกจากระบบถ้าต้องการได้
เป็นระบบสมาชิกคร่าวๆ เพื่อเป็นแนวทางไปประยุตก์ใช้งาน
 
 

สิ่งที่ต้องเตรียม

    ติดตั้ง shared_preferences

 
    ให้เราทำการติดตั้ง shared_preferences สำหรับใช้งานในไฟล์ pubspec.yaml ดังนี้
 
dependencies:
  shared_preferences: ^2.2.3
 
    จากนั้น import ไปใช้งานในหน้าที่ต้องการด้วยคำสั่ง
 
import 'package:shared_preferences/shared_preferences.dart';
 
    เนื้อหานี้เรายังไม่ใช้ Data model สำหรับข้อมูลสมาชิก เรายังไม่ใช้ Provider สำหรับจัดการ
การทำงาน เราจะใช้แค่เพียง shared_preferences เก็บข้อมูลแบบง่ายที่สุด เพื่อให้เห็นการทำงาน
รวมเท่านั้น เพื่อให้เนื้อหากระซับและเจาะจงเฉพาะส่วนการใช้งาน
 
 

ตัวอย่างผลลัพธ์การทำงาน

    ลำดับการทำงาน เมื่อมากดแท็บมายังหน้า profile ซึ่งมีการจำกัดการใช้งานเฉพาะสมาชิกที่ล็อกอินเท่านั้น
เมื่อกดไปหน้าล็อกอิน ยังไม่ได้เป็นสมาชิก ก็สามารถกดต่อไปยังหน้าสมัครสมาชิก หลังจากสมัครสมาชิกเสร็จ
ก็กลับมาหน้าล็อกอิน ทำการทดสอบล็อกอินด้วยข้อมูลที่ไม่ถูกต้อง ล็อกอินไม่่ผ่าน ทำการล็อกอินใหม่อีกครั้ง
ด้วยข้อมูลที่ถูกต้อง ล็อกอินผ่านกลับมายังหน้า profile แสดงข้อความต้อนรับ มีปุ่มล็อกเอาท์
 
 

 
 

 

การใช้งาน shared preferences

    ในการใช้งานระบบสมาชิกอย่างง่าย เราจะต้องมีข้อมูลๆ หนึ่งที่อ้างอิงสถานะการล็อกอินใช้งานหรือสถานะ
สมาชิกที่ล็อกอินแล้ว 
 

    ไฟล์ profile.dart

 
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
 
import 'login.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> {
    // กำหนดตัวแปรใช้งาน SharedPreferences 
    final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
    late bool _loginSuccess;  // กำหดตัวแปรสถานะการล็อกอิน
 
    @override
    void initState() {
      super.initState();
      loadSettings(); // เรียกใช้งานตั้งค่าเมื่อเริ่มต้นเป็นฟังก์ชั่น ให้รองรับ async
    }
 
    // ตั้งค่าเริ่มต้น
    void loadSettings() async {
      // เรียกใช้งาน SharedPreferences ที่เป็น future
      final SharedPreferences prefs = await _prefs; 
      // กำหนดค่า สถานะการล็อกอิน ถ้ามีข้อมูล ถ้าไม่มีให้กำหนดเป็น false
      _loginSuccess = prefs.getBool('loginSuccess') ?? false;
    }   
 
    // เนื่องจากหน้า profile เราต้องการสถานะการล็อกอินไปกำหนดการแสดง
    // จึงสร้างเป็นฟังก์ชั่น คืนค่าสถานะเป็น future<bool>
    Future<bool> getLoginStatus() async {
      final SharedPreferences prefs = await _prefs;
      _loginSuccess = prefs.getBool('loginSuccess') ?? false;
      return _loginSuccess;
    }     
 
    @override
    void dispose() {
      super.dispose();
    }


    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Profile'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                        FutureBuilder<bool>( 
                          future: 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'),
                                              ElevatedButton(
                                                onPressed: () async { // เมื่อล็อกเอาท์
                                                  // ใช้งานข้อมูล SharedPreferences
                                                  final SharedPreferences prefs = await _prefs;
                                                  await prefs.remove("loginSuccess"); // ลบค่าที่บันทึก
                                                  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(() {
                                                  _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);
  }
}
 

    คำอธิบายโค้ดและการทำงาน

 
// กำหนดตัวแปรใช้งาน SharedPreferences 
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late bool _loginSuccess;  // กำหดตัวแปรสถานะการล็อกอิน
 
    เนื่องจากเราจะใช้งานสถานะการล็อกอินจากข้อมูล SharedPreferences ซึ่งเมื่อเรียกใช้งานจะต้องรอข้อมูลก่อน
ดังนั้นสำหรับค่า _loginSuccess เราเลยยังไม่กำหนดค่าเริ่มต้นในทันที่ที่กำหนดตัวแปร แต่จะกำหนดตามทีหลัง
 
// ตั้งค่าเริ่มต้น
void loadSettings() async {
  // เรียกใช้งาน SharedPreferences ที่เป็น future
  final SharedPreferences prefs = await _prefs; 
  // กำหนดค่า สถานะการล็อกอิน ถ้ามีข้อมูล ถ้าไม่มีให้กำหนดเป็น false
  _loginSuccess = prefs.getBool('loginSuccess') ?? false;
}   
 
    เมื่อเปิดมาหน้านี้ และยังไม่มีการล็อกอิน ค่า _loginSuccess จะเป็น false จะแสดงเนื้อหาสำหรับคนทั่วไปหรือ
คนที่ยังไม่ได้ล็อกอิน 
     เราใช้งาน FutureBuilder สำหรับจัดการข้อมูล สถานะการล็อกอินแบบ Future จะคล้ายกับการทำงานของฟ
ฟังก์ชั่น loadSettings() ด้านบน เพียงแต่ว่าฟังก์ชั่นด้านบนจะทำแค่ครั้งแรกครั้งเดียว แต่ฟังก์ชันด้านล่าง เราจะใช้
งานกับการแสดงข้อมูลรองรับหลังจากทำการล็อกอินแล้วด้วย
 
// เนื่องจากหน้า profile เราต้องการสถานะการล็อกอินไปกำหนดการแสดง
// จึงสร้างเป็นฟังก์ชั่น คืนค่าสถานะเป็น future<bool>
Future<bool> getLoginStatus() async {
  final SharedPreferences prefs = await _prefs;
  _loginSuccess = prefs.getBool('loginSuccess') ?? false;
  return _loginSuccess;
}   
 
    ในการแสดงเนื้อหาส่วนของสมาชิก กับที่ไม่เป็นสมาชิก เราจะใช้ Visibility widget จัดการ อย่างไรก็ตาม ก็สามารถ
ใช้ if else แล้วใช้ค่า _loginSuccess สำหรับ return ส่วนเนื้อหาที่ต้องการแทนได้ แล้วแต่จะประยุกต์
    ดูตัวอย่างหน้าตาก่อน และหลังล็อกอิน ดังรูป
 
    ก่อนล็อกอิน

 


 
 
    หลังล็อกอิน
 
 


 
 
    ส่วนของการล็อกเอาท์ เราจำลองการทำงานอย่างง่าย โดยเมื่อกดล็อกเอาท์ ก็ให้ลบข้อมูลส่วนของ 
SharedPreferences ที่เป็นข้อมูล key เท่ากับ loginSuccess ออก
 
onPressed: () async { // เมื่อล็อกเอาท์
  // ใช้งานข้อมูล SharedPreferences
  final SharedPreferences prefs = await _prefs;
  await prefs.remove("loginSuccess"); // ลบค่าที่บันทึก
  setState(() {
    _loginSuccess = false;
  });                      
}, 
 
    การลบช้อมูล SharedPreferences ทำได้ดังนี้
 
await prefs.clear() // ลบข้อมูลทั้งหมด
await prefs.remove("loginSuccess"); // ลบช้อมูลเฉพาะ key ที่ต้องการ
// ดูค่า key ที่บันทึกไว้ทั้งหมด
print(prefs.getKeys());
// {email, password, loginSuccess}
 
 
    ส่วนของการเชื่อมโยงไปหน้าล็อกอิน
 
onPressed: () async {
  // กำหดให้รอค่า หลังจากเปิดไปหน้า lgoin
  final result = await Navigator.push(
    context, 
    MaterialPageRoute(builder: (context) => Login(),
      settings: RouteSettings(
        arguments: null 
      ),
    ),
  );    
  // ถ้ามีการปิดหน้มที่เปิด และส่งค่ากลับมาเป็น true
  if (result == true) {
    setState(() {
      _loginSuccess = true;
    });  
  }
              
}, 
 
    ในการเปิดหน้าล็อกอิน เรากำหนดให้มีการรอการดำเนินการเมื่อปิดหน้าล็อกอิน ผ่านตัวแปร result
ความหมายก็คือ เมื่อเปิดหน้าล็อกอินด้วย คำสั่ง push() ซึ่งเป็นการซ้อนหน้าใหม่ไว้ด้านบนแล้ว ให้รอ
รับข้อมูลจากหน้าล็อกอินผ่านตัวแปร result  ซึ่งในตัวอย่าง ถ้ามีการส่งค่า true กลับมา นั่นก็คือมีการ
ล็อกอินผ่าน ก็ให้กำหนดค่า _loginSuccess เป็น true
 

    ไปต่อกันที่ไฟล์ login.dart

 
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.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> {
    late final SharedPreferences prefs;
   
    // สร้างฟอร์ม key หรือ id ของฟอร์มสำหรับอ้างอิง
    final _formKey = GlobalKey<FormState>();
 
    // กำหนดตัวแปรรับค่า
    final _email = TextEditingController();
    final _password = TextEditingController();
  
    @override
    void initState() {
      super.initState();
      loadSettings();
    }
 
    void loadSettings() async {
      prefs = await SharedPreferences.getInstance();
    }    
 
    // กำหนดสถานะการแสดงแบบรหัสผ่าน
    bool _isHidden = true;    
    bool _authenticatingStatus = false;    
 
    @override
    void dispose() {
      _email.dispose(); // ยกเลิกการใช้งานที่เกี่ยวข้องทั้งหมดถ้ามี
      _password.dispose(); 
      super.dispose();
    }    
 
    @override
    Widget build(BuildContext context) {
   
        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;
                                    });
                                    // จำลองการหน่วงเวลา
                                    await Future.delayed(const Duration(seconds: 3));      
                                    // อ้างอิงฟอร์มที่กำลังใช้งาน ตรวจสอบความถูกต้องข้อมูลในฟอร์ม
                                    if (_formKey.currentState!.validate()) { //หากผ่าน 
                                      // จำลองดึงข้อมูลจาก SharedPreferences
                                      String email = prefs.getString("email") ?? '';
                                      String password = prefs.getString("password") ?? '';
 
                                      // จำลองเปรียบเทียบค่า เพื่อทำการล็อกอิน  
                                      if(email == _email.text && password == _password.text){
                                        ScaffoldMessenger.of(context).showSnackBar(
                                          const SnackBar(content: Text('Login Successful')),
                                        );
                                        // กำหนดค่าให้กับ SharedPreferences จำลองล็อกอินผ่าน
                                        await prefs.setBool("loginSuccess", true);
                                        await Future.delayed(const Duration(seconds: 2));    
                                        Navigator.pop(context, true);   // ปิดหน้านี้พร้อมคืนค่า true 
                                      }else{
                                        // จำลองล็อกอินไม่ผ่าน
                                        ScaffoldMessenger.of(context).showSnackBar(
                                          const SnackBar(content: Text('Login fail..  try agin!')),
                                        );
                                        await Future.delayed(const Duration(seconds: 2));   
                                        // เปลี่ยนสถานะเป็นกำลังล็อกอิน 
                                        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
                                            ),
                                          ),
                                        );    
                                    },
                                  )
                                ],
                              )
                          ],
                      )
                  ),
                ),
              ),
            ),
        );
    }
}
 
 
    ในการจำลองหน้าล็อกอินเราตัดส่วนของการการ validate ข้อมูลออกไป เพื่อให้โค้ดกระชับ ในหน้านี้ เราก็ยัง
มีการใช้งานข้อมูล SharedPreferences ผ่านตัวแปร prefs และมีการกำหนดตัวแปรชื่อ _authenticatingStatus
เพื่อใช้เป็นสถานะ กำลังทำการล็อกอิน  เราใช้สถานะนี้เป็นตัวกำหนดการแสดง ปุมล็อกอินกับตัวข้อมูลตัว loading
ว่ากำลังทำการล็อกอิน เข้าใจอย่างง่ายก็คือ เมื่อเรากดปุ่มล็อกอิน ตัว loading ก็จะแสดงขึ้นมาแทน ถ้าล็อกอินไม่ผ่าน
สถานะ _authenticatingStatus ก็จะกลับมาเป็น false ให้กดล็อกอินใหม่ แบบนี้เป็นต้น
    มาเริ่มกันส่วนของกรณีที่ยังไม่เป็นสมาชิก หรือยังไม่สมัคร เรามีปุ่มสำหรับเปิดไปหน้าสมัครสมาชิกหรือ register
ด้วยรูปแบบคำสั่งดังนี้
 
onTap: () async {
  // เปิดหน้า สมัครสมาชิก โดย ซ้อนหน้า ล็อกอินเดิม ใช้ push แทน
  // ไม่เช่นนั้นจะไม่มีค่าส่งกลับจากหน้าล็อกอิน ที่เรารออรับค่าอยู่		
  // Navigator.pushReplacement(
  Navigator.push(
    context, 
    MaterialPageRoute(builder: (context) => Register(),
      settings: RouteSettings(
        arguments: null 
      ),
    ),
  );    
},
 
    จะเห็นว่าในการเปิดหน้า register เราใช้คำสั่ง pushReplacement() แทน push() ซึ่งก็คือ แทนที่จะเปิดเป้นหน้า
ใหม่ซ้อนบนหน้า login ก็ให้เปลี่ยนจากหน้า login เป็นหน้า register แทน ดังนั้นถ้ากดปุ่ม back ที่เครื่องหรือปุ่ม back
อัตโนมัติมุมบนซ้าย ก็จะเป็นการย้อนไปหน้า profile เลย ไม่ใช่ย้อนไปหน้า login  
 
    มาดูต่อส่วนของการจำลองการล็อกอิน
 
onPressed: () async {
  // เปลี่ยนสถานะเป็นกำลังล็อกอิน
  setState(() {
    _authenticatingStatus = !_authenticatingStatus;
  });
  // จำลองการหน่วงเวลา
  await Future.delayed(const Duration(seconds: 3));      
  // อ้างอิงฟอร์มที่กำลังใช้งาน ตรวจสอบความถูกต้องข้อมูลในฟอร์ม
  if (_formKey.currentState!.validate()) { //หากผ่าน 
    // จำลองดึงข้อมูลจาก SharedPreferences
    String email = prefs.getString("email") ?? '';
    String password = prefs.getString("password") ?? '';

    // จำลองเปรียบเทียบค่า เพื่อทำการล็อกอิน  
    if(email == _email.text && password == _password.text){
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Login Successful')),
      );
      // กำหนดค่าให้กับ SharedPreferences จำลองล็อกอินผ่าน
      await prefs.setBool("loginSuccess", true);
      await Future.delayed(const Duration(seconds: 2));    
      Navigator.pop(context, true);   // ปิดหน้านี้พร้อมคืนค่า true 
    }else{
      // จำลองล็อกอินไม่ผ่าน
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Login fail..  try agin!')),
      );
      await Future.delayed(const Duration(seconds: 2));   
      // เปลี่ยนสถานะเป็นกำลังล็อกอิน 
      setState(() {
        _authenticatingStatus = !_authenticatingStatus;
      });
    }                                       
  }                                
},
 
    ในตัวอย่าง เราจำลองการหน่วงเวลา เพื่อให้เสมือนว่ากำลังทำงานโดยใช้งาน
 
await Future.delayed(const Duration(seconds: 2));    
 
    ทันทีที่มีการกดปุ่ม ล็อกอิน เราก็เปลี่ยนสถานะกำลังล็อกอินเป็น true
 
// เปลี่ยนสถานะเป็นกำลังล็อกอิน
setState(() {
  _authenticatingStatus = !_authenticatingStatus;
});
 
    ในตัวอย่างเรากำหนดเป็นค่าตรงข้าม นั่นก็คือเหมือน toggle สลับจากค่าหนึ่งเป็นค่าตรงข้าม
 
// จำลองดึงข้อมูลจาก SharedPreferences
String email = prefs.getString("email") ?? '';
String password = prefs.getString("password") ?? '';
 
    การเรียกดูข้อมูลจาก SharedPreferences
 
ข้อมูลจาก SharedPreferences เราเรียกผ่านตัวแปร prefs ซึ่งกำหนดไว้ในตอนต้นเรียบร้อยแล้ว
และเนื่องจากค่าใน SharedPreferences จะใช้เก็บข้อมูลที่ไม่ได้มีขนาดใหญ่อะไร เราจึงสามารถ
ใช้งานข้อมูลผ่านคำสั่ง  ดังนี้
 
prefs.getBool(String key) // สำหรับข้อมูล boolean
prefs.getDouble(String key) // สำหรับข้อมูล double
prefs.getInt(String key) // สำหรับข้อมูล int
prefs.getKeys() // ดูข้อมูล key ทั้งหมด คืนค่าเป็น Set<String>
prefs.getString(String key)  // สำหรับข้อมูล String
prefs.getStringList(String key) // สำหรับข้อมูล List<String>

// หากเรียกใช้งานไม่ตรงตามชนิดข้อมูลก็จะเกิด error 
 
    เมื่อทำการส่งข้อมูลฟอร์ม เราก็จำลองดึงค่าจากข้อมูล SharedPreferences ถ้ามี (สมัครสมาชิกแล้ว)
มาไว้ในตัวแปร จากนั้นจำลองการเปรียบเทียบกับข้อมูลในฟอร์มอย่างง่าย
 
if(email == _email.text && password == _password.text){
 
    ถ้าล็อกอินผ่าน
 
// กำหนดค่าให้กับ SharedPreferences จำลองล็อกอินผ่าน
await prefs.setBool("loginSuccess", true);
await Future.delayed(const Duration(seconds: 2));    
Navigator.pop(context, true);   // ปิดหน้านี้พร้อมคืนค่า true 
 
    บันทึกข้อมูลสถานะการล็อกอินไว้ใน SharedPreferences จากนั้นก็ปิดหน้าล็อกอิน พร้อมส่งค่า true กลับ
ไปเพื่อกำหนดให้หน้า profile โหลดส่วนของ Widget build() อีกครั้ง อย่าลืมว่า คำสั่ง pop() เป็นการปิดหน้า
login ที่อยู่ด้านบนของหน้า profile ซึ่งเปิดไว้อยู่แล้ว การกลับไปหน้า profile ในรูปแบบคำส่ังนี้จึงไม่ใช่การ
โหลดหน้า profile ใหม่ เราจึงต้องส่งค่ากลับไป เพื่อไปกำหนดให้ state เปลี่ยนแปลง และทำคำสัง build() 
อีกครั้ง  
    อย่างไรก็ตามการกำหนดคำสั่งที่จะกลับไปหน้าเดิม ก็ขึ้นกับโครงสร้างการทำงานด้วย ไม่เจาะจงว่าจะต้อง
เป็นคำสั่ง pop() เพราะ route ที่ใช้งานแต่ละโปรเจ็คอาจจะไม่เหมือนกัน
    
    ในกรณีล็อกอินไม่ผ่าน เราก็ทำการเปลี่ยนค่าสถานะกำลังล็อกอินกลายเป็น false อีก เพื่อซ่อนตัว loading
และแสดงปุ่มล็อกอินสำหรับลองใหม่อีกครั้ง
 
    ตัวอย่างหน้ากำลังล็อกอิน
 
 


 
 
 
    ไปต่อที่ส่วนสุดท้าย หน้าสมัครสมาชิก 
 

    ไฟล์ register.dart

 
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.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> {
    late final SharedPreferences prefs;
    
    // สร้างฟอร์ม key หรือ id ของฟอร์มสำหรับอ้างอิง
    final _formKey = GlobalKey<FormState>();
 
    // กำหนดตัวแปรรับค่า
    final _email = TextEditingController();
    final _password = TextEditingController();
 
    // กำหนดสถานะการแสดงแบบรหัสผ่าน
    bool _isHidden = true;    
    bool _registeringStatus = false;    
  
    @override
    void initState() {
      super.initState();
      loadSettings();
    }
 
    void loadSettings() async {
      prefs = await SharedPreferences.getInstance();
    }
 
    @override
    void dispose() {
      _email.dispose(); // ยกเลิกการใช้งานที่เกี่ยวข้องทั้งหมดถ้ามี
      _password.dispose(); 
      super.dispose();
    }
 
    @override
    Widget build(BuildContext context) {
   
        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;
                                  });
                                  await Future.delayed(const Duration(seconds: 3));      
                                  if (_formKey.currentState!.validate()) { //หากผ่าน 
                                    // กำหนดค่าให้กับ SharedPreferences จากข้อมูลฟอร์ม
                                    await prefs.setString("email", _email.text);
                                    await prefs.setString("password", _password.text);
 
                                    ScaffoldMessenger.of(context).showSnackBar(
                                      const SnackBar(content: Text('Create new user Successful')),
                                    );
                                    await Future.delayed(const Duration(seconds: 2));   
                                    // กลับไปยังหน้าล็อกอิน โดยปิดหน้านี้จากที่กำลังซ้อนอยู่
                                    Navigator.pop(context);   // ปิดหน้านี้
                                    // เราไม่ใช้การเปลี่ยนหน้าด้วย pushReplacement เพื่อให้หน้า login ส่งค่ากลับได้
                                    /* Navigator.pushReplacement(
                                        context, 
                                        MaterialPageRoute(builder: (context) => Login(),
                                          settings: RouteSettings(
                                            arguments: null
                                          ),
                                        ),
                                    ); */                                                                           
                                  }                                                           
                                },
                                  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
                                          ),
                                        ),
                                      );   */  
                                    },
                                  )
                                ],
                              )
                          ],
                      )
                  ),
                ),
              ),
            ),
        );
    }
}
 
    ส่วนของหน้าสมัครสมาชิกก็ใช้คล้ายๆ กับหน้าล็อกอิน ผู้ใช้กรอกข้อมูลแค่ อีเมลกับรหัสผ่าน เพื่อสมัครสมาชิก
และเมื่อกดปุ่ม regrister ก็นำข้อมูลที่กรอก เก็บลงใน SharedPreferences ดูส่วนของการทำงานเมื่อทีการ
กรอกข้อมูลและบันทึก
 
onPressed: () async {
  // เปลี่ยนสถานะกำลังสมัครสมาชิก
  setState(() {
    _registeringStatus = !_registeringStatus;
  });
  await Future.delayed(const Duration(seconds: 3));      
  if (_formKey.currentState!.validate()) { //หากผ่าน 
    // กำหนดค่าให้กับ SharedPreferences จากข้อมูลฟอร์ม
    await prefs.setString("email", _email.text);
    await prefs.setString("password", _password.text);

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Create new user Successful')),
    );
    await Future.delayed(const Duration(seconds: 2));   
    // กลับไปยังหน้าล็อกอิน โดยปิดหน้านี้จากที่กำลังซ้อนอยู่
    Navigator.pop(context);   // ปิดหน้านี้                                                                         
  }                                                           
},
 
    การบันทึกข้อมูลลง SharedPreferences จะใช้รูปแบบคำสั่งต่างๆ ดังนี้
 
await prefs.setBool(String key, bool value) // คืนค่าเป็น Future<bool> true / false
await prefs.setDouble(String key, double value) // คืนค่าเป็น Future<bool> true / false
await prefs.setInt(String key, int value) // คืนค่าเป็น Future<bool> true / false
await prefs.setString(String key, String value) // คืนค่าเป็น Future<bool> true / false
await prefs.setStringList(String key, List<String> value) // คืนค่าเป็น Future<bool> true / false
 
เนื่องจากการบันทึกข้อมูล หากต้องการผลลัพธ์ว่าบันทึกสำเร็จหรือไม่ เราต้องใช้งานร่วมกับ await เพราะข้อมูล
ที่คืนกลับมาเป็นข้อมูล Future<bool> true / false  
    ข้อมูลที่จะบันทึกต้องมีชนิดข้อมูลที่สอดคล้องกับคำสั่งที่ใช้งานด้วย ทุกคำสั่ง set ที่เรียกใช้งานในชื่อ key
ซ้ำจะหมายถึงการอัพเดทค่าข้อมูลเดิม จะไม่ใช่การบันทึกค่าใหม่ใน key เดิม เข้าใจอย่างง่ายก็คือ ใน app จะมี
key ที่ไม่ซ้ำกัน 
 
    ตัวอย่างการกรอกข้อมูล เพื่อสมัครสมาชิก
 
 


 
 
    ในหน้าสมัครสมาชิก เรามีลิ้งค์สำหรับกลับไปหน้าล็อกอิน กรณีเป็นสมาชิกอยู่แล้ว หรือถ้าเป็นสมาชิกใหม่
และต้องการสมัครสมาชิก ก็ทำการกรอกข้อมูล จากนั้นกดปุ่ม Register เมื่อสมัครสมาชิกเรียบร้อยแล้วก็จะกลับ
ไปหน้าล็อกอินให้อัตโนมัติ เพื่อทำการล็อกอินเข้าใช้งานตามข้อมูลที่กรอกล่าสุด    
 
    เนื้อหานี้เราจะเน้นไปที่การใช้งานข้อมูล SharedPreferences และภาพรวมระบบสมาชิก เป็นแนวทางสำหรับ
เนื้อหาต่อๆ ไป ซึ่งเราจะมีการใช้งาน provider รวมถึงใช้งานร่วมกับข้อมูลจาก server จำลอง จะเป็นยังไง
รอติดตามในบทความต่อไป


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 30-07-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

http://niik.in/download/flutter/demo_025_30072024_source.rar


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง






เนื้อหาพิเศษ เฉพาะสำหรับสมาชิก

กรุณาล็อกอิน เพื่ออ่านเนื้อหาบทความ

ยังไม่เป็นสมาชิก

สมาชิกล็อกอิน



( หรือ เข้าใช้งานผ่าน Social Login )




URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ