Flutter Phone Authentication and System Read User Data With Provider
Bismillah, kali ini saya akan berbagi bagaimana best practice cara membaca data user yang telah melakukan authentication pada aplikasi yang di bangun menggunakan Flutter dengan State Management Provider. Sebelum ke pembahasan lebih lanjut ada beberapa hal yang harus di pahami terlebih dahulu sebagai berikut:
State Management
Definisi State Management
State Management atau dalam bahasa indonesia Manajemen Negara mengacu pada manajemen keadaan satu atau lebih kontrol antarmuka pengguna seperti bidang teks, tombol OK, tombol radio, dan lain-lain. Dalam antarmuka pengguna grafis. Dalam teknik pemrograman antarmuka pengguna ini, keadaan satu kontrol UI tergantung pada keadaan kontrol UI lainnya.
Di Flutter sendiri sebenarnya sudah memiliki management state bawaannya yaitu dengan menggunakan setState() yang biasa digunakan pada class Statefull Widget.
Cara kerjanya yaitu state akan mengambil semua data yang diperlukan untuk membangun User Interface (UI) pada waktu tertentu dan dijalankan di memori perangkat baik itu assets, variable, font, dan yang lainnya.
Secara konsep state terbagi menjadi local state dan app state.
Local State
Sebagai contoh local state kita bisa lihat pada kode berikut,
class _MyHomepageState extends State<MyHomepage> {
int _index = 0;
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: _index,
onTap: (newIndex) {
setState(() {
_index = newIndex;
});
},
// ... items ...
);
}
}
Variabel _index merupakan local variable atau juga bisa disebut dengan local state karena bersifat private sehingga tidak bisa diakses di kelas lain. Salah satu cara yang umum untuk memperbarui variabel tersebut kita dapat menggunakan cara setState() di dalam kelas yang berisi variabel tersebut.
App State
Pada beberapa kasus tertentu membutuhkan state yang dapat berjalan di seluruh layar aplikasi atau juga bisa disebut dengan app state. Berdasarkan dokumentasi Flutter untuk mengimplementasikannya terdapat beberapa cara yaitu InheritedWidget, InheritedModel, Redux, Fish-Redux, BLoC, GetIt, MobX, Binder, GetX, Riverpod, dan Provider.
Provider
Provider ini sendiri merupakan salah satu managemen state yang mana isinya adalah sebuah design coding yang mana kita memisahkan antara logic dan view kita bertujuan agar logic tersebut juga bersifat re-usable.
Kalau kita sebelumnya sudah mengenal java atau kotlin maka ini sama saja seperti design pattern yang mana ada MVP dan MVVM jika di web maka kita mengenal MVC.
Bagaimana sampai sini semoga sudah bisa memahami apa itu state management ya, mari kita lanjut!
Firebase Authentication
Dalam membangun sebuah aplikasi biasanya terdapat authentication yang mana authentication ini berguna untuk memastikan user yang masuk aplikasi adalah user yang sebenarnya memiliki identitas. Proses ini memastikan supaya kalau ada yang mengaku sebagai orang lain bisa terbaca sebagai orang lain.
Setelah melakukan authentication pada sebuah aplikasi akan membutuhkan sistem untuk mengecek dan membacanya.
Pada kasus kali ini saya akan mencontohkan sistem authentication dengan OTP dan pembacaan data user secara global nantinya agar bisa di panggil di mana saja pada aplikasi.
Siapkan segelas kopi dan cemilan secukupnya, mari kita lanjut ke praktik!
Persiapan Flutter Project
Pertama-tama buka terminal dan posisikan ke dalam folder yang anda inginkan, contoh saya akan menyimpan project ini ke dalam folder Documents/Projects/FlutterProject/Research/.
Lalu buatlah project Flutter baru dengan menggunakan printah
flutter create --org my.id.sendiagustian snippetauth
Jika selesai akan tampil output seperti berikut,
Buka folder project dengan Text Editor atau IDE favorite anda.
Persiapan Firebase Project
Kita perlu menyiapkan Firebase Project kita yang nantinya kita hubungkan ke aplikasi yang kita buat.
- Buka dan buat project Firebase melalui halaman website officialnya disini firebase.google.com.
-
Selanjutnya klik Get started tombol seperti pada gambar berikut,
-
Lalu anda akan di larikan ke halaman
console.firebase.google.com di sini anda tambahkan project firebasenya.
-
Berikutnya, masukkan nama Proyek & pilih lokasi Analytics. Lokasi
analitik mewakili negara/wilayah organisasi Anda dan menetapkan mata uang
untuk pelaporan pendapatan. Buatkan dan persiapkan firebase project lalu
tambahkan aplikasi ikutilah langkah-langkah yang telah di
intruksikan.
-
Setelah proyek berhasil dibuat, Firebase akan menampilkan prompt yang
mengatakan Your new project is ready. Klik Lanjutkan untuk menyelesaikan
alur.
-
Sampai sini project Firebase kita telah siap
Konfigurasi aplikasi dengan Firebase
Firebase ini bisa di konfigurasikan dengan aplikasi Android, Ios, dan WEB.
Untuk kali ini saya hanya akan konfigurasikan dengan aplikasi android.
Untuk konfigurasinya ikuti langkah-langkah berikut,
-
Di bagian Add an app to get started, klik ikon android untuk menambahkan
aplikasi Android ke Firebase.
-
Pada layar berikutnya, masukkan nama paket Android & Nama panggilan
aplikasi Anda dan klik Daftar Aplikasi. Nama paket Anda biasanya ada
applicationId di build.gradle file tingkat aplikasi Anda. Jika
ditentukan, nama panggilan aplikasi akan digunakan di seluruh konsol
Firebase untuk mewakili aplikasi ini. Nama panggilan tidak terlihat oleh
pengguna.
Untuk persiapan kita juga siapkan sertifikat SHA-1 agar proses debug authentication bisa dijalankan, karena untuk authentication ini memerlukan sertifikat SHA-1, untuk menyiapkan ini kinjungi client auth lalu masukan.
-
Unduh google-services.json file & letakkan di
app direktori root proyek Anda. Pastikan nama file konfigurasi tidak
ditambahkan dengan karakter tambahan, seperti (2).
-
Buka folder android file build.gradle lalu tambahkan
baris berikut,
buildscript { repositories { google() // Google's Maven repository } dependencies { ... // Add this line classpath 'com.google.gms:google-services:4.3.10' } } - Buka folder android/app file build.gradle lalu tambahkan baris berikut di paling bawah
apply plugin: 'com.google.gms.google-services'
masih di folder android/app file build.gradle ubah minSdkVersion menjadi 23 seperti berikut
defaultConfig {
applicationId "my.id.sendiagustian.snippetauth"
minSdkVersion 23
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
firebase_auth: 3.1.4
firebase_core: 1.8.0
cloud_firestore: 2.5.4
provider: 6.0.1
country_list_pick: 1.0.1+5
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
Mempersiapkan Struktur Folder Project
Sebelum melakukan code lebih jauh alangkah baiknya kita mempersiapkan dulu struktur project yang akan kita pakai seperti apa agai nantinya file-file code kita tidak berantakan. Yang paling unum dan sederhana kita akan memisahkan code-code untuk UI dan Logic nantinya.
Untuk struktur folder project yang akan saya praktekkan nanti kurang lebih akan ada databases, models, screens dan widgets.
Oke, disini kita siap melakukan code lebih lanjut lagi.
Menyiapkan Widget
Pertama-tama kita akan meniapkan dulu Widget yang kemungkinan akan terus menerus dipanggil.
Buat file app_widget.dart di folder widgets lalu tuliskan kode berikut,
import 'package:flutter/material.dart';
class AppWidget {
static void loadingPageIndicator({
required BuildContext context,
}) {
showGeneralDialog(
context: context,
barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 100),
barrierLabel: '',
barrierColor: Colors.black.withOpacity(0.6),
pageBuilder: (context, animation1, animation2) {
return Container();
},
transitionBuilder: (BuildContext context, a1, a2, widget) {
return Transform.scale(
scale: a1.value,
child: Opacity(
opacity: a1.value,
child: WillPopScope(
onWillPop: () => Future.value(false),
child: const Center(
child: SizedBox(
width: 35,
height: 35,
child: CircularProgressIndicator(),
),
),
),
),
);
},
);
}
// Untuk manampilkan snackbar
static ScaffoldFeatureController showSnackBar({
required BuildContext context,
required Widget content,
required Duration duration,
}) {
return ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: content,
duration: duration,
),
);
}
}
kode di atas adalah untuk membuat kelas AppWidget yang di dalamnya terdapat static void loadingPageIndicator() dan static ScaffoldFeatureController showSnackBar(), kelas AppWidget ini nantinya akan sering kita panggil.
static void loadingPageIndicator() ini berfungsi untuk menunggu proses fungsi yang berjalan selesai ketika mulai dijalankan nantinya.
Sedangkan static ScaffoldFeatureController showSnackBar() ini berfungsi untuk menampilkan SnackBar yang biasa digunakan untuk menampilkan info kesalahan atau lainnya.
Menyiapkan UI Screen
Buka main.dart lalu tambahkan kode berikut,
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/auth_checker.dart';
...
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Snippet Phone Auth',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthChecker(),
);
}
}
di sini kita belum membuat kelas AuthChecker() nya mari kita buat juga, buat folder auth di dalam folder screens dan buat file auth_checker.dart lalu ketikan kode berikut,
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/login.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
import 'package:snippetphoneauth/screens/home.dart';
// Auth status pengecekan
class AuthChecker extends StatelessWidget {
const AuthChecker({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer(builder: (_, AuthProvider authProvider, __) {
// Pengecekan variable auth status untuk menentukan route ke halaman
if (authProvider.auth.currentUser != null) {
return const HomeScreen();
} else {
// masuk ke halaman login jika user auth tidak terisi
return const LoginScreen();
}
});
}
}
Kita juga perlu menyiapkan kelas AuthProvider(), di dalam folder auth buat folder providers dan buat file auth_provider.dart ketikkan kode di bawah ini,
import 'package:firebase_auth/firebase_auth.dart';
class AuthProvider with ChangeNotifier {
// Pendekralasian/inisiasi object auth firebase
FirebaseAuth auth = FirebaseAuth.instance;
}
Siapkan juga kelas HomeScreen() di folder screens buat file home.dart ketikan kode di bawah ini
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Snippet Phone Auth'),
actions: [
IconButton(
onPressed: () {
//TODO
},
icon: const Icon(
Icons.logout,
),
),
],
),
body: const Center(child: Text('Home Screen')),
);
}
}
Sekarang kita buat kelas LoginScreen() di folder auth buat file login.dart ketikkan kode berikut,
import 'package:country_list_pick/country_list_pick.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => exit(0),
child: Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.only(top: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 16, top: 40),
child: Container(
padding: const EdgeInsets.only(bottom: 18),
child: const Text(
"Snippet Auth",
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
fontSize: 25,
),
),
),
),
const Padding(
padding: EdgeInsets.only(left: 16, top: 16),
child: Text(
"Masuk",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
const Padding(
padding: EdgeInsets.only(left: 16, top: 8),
child: Text(
"Silahkan masuk dengan nomor HP-mu",
style: TextStyle(
fontSize: 14,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 30),
child: RichText(
text: const TextSpan(
text: 'Nomor HP',
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: 14,
),
children: [
TextSpan(
text: ' *',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 14,
),
)
],
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 10, right: 16),
child: Row(
children: <Widget>[
Container(
height: 35,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(15),
),
child: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return CountryListPick(
appBar: AppBar(
title: const Text('Pilih Kode Negara'),
),
theme: CountryTheme(
isShowFlag: true,
isShowTitle: true,
isShowCode: true,
isDownIcon: true,
showEnglishName: false,
),
initialSelection: authProvider.countryCode.dialCode,
onChanged: (CountryCode? code) {
authProvider.countryCode = code!;
},
pickerBuilder: (context, CountryCode? countryCode) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
countryCode!.dialCode!,
style: const TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
const Icon(
Icons.arrow_drop_down,
color: Colors.white,
)
],
);
},
);
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 16),
child: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return Form(
child: TextFormField(
onChanged: (String text) {
authProvider.notifyState();
},
keyboardType: TextInputType.phone,
controller: authProvider.phoneNumberController,
decoration: InputDecoration(
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
),
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
border: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
),
),
hintText: "8XX-XXXX-XXX",
suffixIcon: IconButton(
onPressed: () {
authProvider.phoneNumberController
.clear();
},
icon: const Icon(
Icons.clear,
color: Colors.black87,
),
),
),
),
);
},
),
),
)
],
),
)
],
),
),
floatingActionButton: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return FloatingActionButton(
backgroundColor:
authProvider.phoneNumberController.text.length > 8
? Colors.blue
: Colors.grey[400],
onPressed: () {
// TODO
},
child: const Icon(
Icons.arrow_forward,
),
);
},
),
),
);
}
}
karena di halaman login ini kita sudah memerlukan beberapa state maka kita akan mulai membuat state dengan membuat getter dan setter yang diperlukan dihalaman login ini.
di file auth_provider.dart tadi kita tambahkan kode menjadi seperti berikut,
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:country_list_pick/country_list_pick.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
// Provider untuk halaman auth
class AuthProvider with ChangeNotifier {
...
// Pendekralasian/inisiasi object firebase firestore
FirebaseFirestore firestore = FirebaseFirestore.instance;
// Deklarasi variabel Varification Id untuk auth
String? verificationId;
// Deklarasi variabel country code untuk menyimpan code
CountryCode _countryCode = CountryCode(dialCode: '+62');
// Deklarasi variabel _phoneNumberController, untuk Controller TextField
TextEditingController _phoneNumberController = TextEditingController();
// getter untuk menampilkan var _phoneNumberController
TextEditingController get phoneNumberController => _phoneNumberController;
// Setter untuk mengubah controller textfield _phoneNumberController
set phoneNumberController(TextEditingController phoneNumberController) {
_phoneNumberController = phoneNumberController;
notifyListeners();
}
// Deklarasi variabel _otpCodeController, untuk Controller TextField
TextEditingController _otpCodeController = TextEditingController();
// getter untuk menampilkan var _otpCodeController
TextEditingController get otpCodeController => _otpCodeController;
// Setter untuk mengubah controller textfield _otpCodeController
set otpCodeController(TextEditingController otpCodeController) {
_otpCodeController = otpCodeController;
notifyListeners();
}
// getter untuk menampilkan countryCode
CountryCode get countryCode => _countryCode;
// Setter untuk mengubah countrycode
set countryCode(CountryCode countryCode) {
_countryCode = countryCode;
notifyListeners();
}
// Untuk notis state kosong
void notifyState() {
notifyListeners();
}
}
Karena kita sudah menggunakan Consumer maka jangan lupa kita tambahkan dulu ChangeNotifierProvider di posisi paling atasnya kita akan simpan ini di atas widget MaterialApp agar state yang tersimpan bisa kita panggil kapan saja di dalam aplikasi nantinya dan ini yang dinamakan App State yang sudah dijelaskan diatas tadi.
Buka file main.dart lalu tambahkan kode menjadi seperti berikut,
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/auth_checker.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
...
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: AuthProvider(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Snippet Phone Auth',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthChecker(),
),
);
}
}
Sampai disini seharusnya jika tidak ada error atau kesalahan bisa kita coba jalankan projectnya dulu, hasil yang diharapkan ketika selesai dijalankan ini akan menampilkan halaman login terlebih dahulu.
Seperti tampilan berikut,
Jika sudah bisa dijalankan alhamdulillah keren, namun jika ada kesalahan atau hal yang tidak di pahami sampai sini coba kalian tuliskan pertanyaan di kolom komentar ya.
Kita lanjut dulu sebelum membuat fungsi loginnya sekarang kita perlu membuat UI input OTP terlebih dahulu. Mari kita buat, di folder auth buat file verification.dart lalu ketikkan kode berikut,
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
class VerificationScreen extends StatelessWidget {
final String beUserPhone;
const VerificationScreen({
Key? key,
required this.beUserPhone,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.only(left: 5, top: 48),
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
),
),
Container(
padding: const EdgeInsets.only(left: 16, top: 15),
child: const Text(
"Kode OTP sudah di kirim!",
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.only(left: 16, top: 8, right: 16),
child: Text(
"Masukkan kode OTP yang kami kirim ke nomor HP-mu, $beUserPhone.",
maxLines: 3,
style: const TextStyle(
fontSize: 14,
),
),
),
Container(
padding: const EdgeInsets.only(left: 16, top: 30),
child: RichText(
text: const TextSpan(
text: 'Kode OTP',
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: 14,
),
children: [
TextSpan(
text: ' *',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 14,
),
)
],
),
),
),
Container(
padding: const EdgeInsets.only(left: 16, top: 10),
child: Row(
children: <Widget>[
Expanded(
child: Form(
child: Container(
padding: const EdgeInsets.only(left: 0, right: 16),
child: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return TextFormField(
maxLength: 6,
onChanged: (String text) {
authProvider.notifyState();
},
keyboardType: TextInputType.number,
controller: authProvider.otpCodeController,
decoration: InputDecoration(
counterText: '',
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
border: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
hintText: " • • • • • •",
suffixIcon: IconButton(
onPressed: () {
authProvider.otpCodeController.clear();
},
icon: const Icon(
Icons.clear,
color: Colors.black87,
),
),
),
);
},
),
),
),
)
],
),
),
],
),
floatingActionButton: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return FloatingActionButton(
backgroundColor: authProvider.otpCodeController.text.length > 5
? Colors.blue
: Colors.grey[400],
onPressed: () {
// TODO
},
child: const Icon(
Icons.arrow_forward,
),
);
},
),
);
}
}
Nah sampai sini kita telah selesai menyiapkan UI screen aplikasi kita.
Menyiapkan Model dan Database
Sebelum membuat fungsi loginnya kita akan membutuhkan model data dan query ke database nantinya mari kita siapkan terlebih dahulu.
Di dalam folder lib buatkan folder models dan databases.
Kita akan buatkan dari model terlebih dahulu buatkan file user_model.dart di dalam folder models tadi yang sudah di buat, lalu ketikan kode berikut,
import 'package:cloud_firestore/cloud_firestore.dart';
class UserModel {
late final String docUID;
late final String phone;
late final String status;
late final DateTime? logedInDate;
UserModel({
required this.docUID,
required this.phone,
required this.status,
this.logedInDate,
});
factory UserModel.fromData(DocumentSnapshot doc) {
return UserModel(
docUID: doc.id,
phone: doc.get('phone'),
status: doc.get('status'),
logedInDate: doc.get('logedInDate').toDate(),
);
}
Map<String, Object?> toData() {
return {
'docUID': docUID,
'phone': phone,
'status': status,
'logedInDate': logedInDate,
};
}
static UserModel get initialData {
return UserModel(
docUID: '',
phone: '',
status: '',
);
}
}
Selanjutnya kita buat file snippet_auth_database.dart di dalam folder databases lalu ketikan kode berikut,
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:snippetphoneauth/models/user_model.dart';
class PhoneAuthDatabase {
FirebaseFirestore firestore = FirebaseFirestore.instance;
String collectionName = 'snippet-phone-auth';
// Get user data
Future<DocumentSnapshot> getUserData(String uid) async {
return await firestore.collection(collectionName).doc(uid).get();
}
// Create new user
Future createUser(UserModel userModel) async {
try {
await firestore
.collection(collectionName)
.doc(userModel.docUID)
.set(userModel.toData());
} catch (e) {
debugPrint(e.toString());
}
}
// Update data user
Future updateDataUser(String uid, {required var data}) async {
try {
await firestore.collection(collectionName).doc(uid).update(data);
} catch (e) {
debugPrint(e.toString());
}
}
}
Oke, Model dan Databases sudah siap kita gunakan.
Membuat Fungsi Authentication
Karena segala kebutuhan kita sudah disiapkan sebelumnya sekarang kita langsung buatkan fungsi authentication OTPnya saja.
Di file auth_provider.dart kita tambahkan kode dengan fungsi-fungsi menjadi seperti berikut,
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:country_list_pick/country_list_pick.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:snippetphoneauth/databases/snippet_auth_database.dart';
import 'package:snippetphoneauth/models/user_model.dart';
import 'package:snippetphoneauth/screens/auth/auth_checker.dart';
import 'package:snippetphoneauth/screens/auth/verification.dart';
import 'package:snippetphoneauth/widgets/app_widget.dart';
// Provider untuk halaman auth
class AuthProvider with ChangeNotifier {
...
// Fungsi stream get data user ke collection yang dijalankan oleh StremProvider
Stream<UserModel>? get user {
// pengecekan user login auth
if (auth.currentUser != null) {
// Nilai dikembalikan untuk mengupdate data model user data
return firestore
.collection('snippet-phone-auth')
.doc(auth.currentUser!.uid)
.snapshots()
.map((userModel) {
return UserModel.fromData(userModel);
});
} else {
// Nilai dikembalikan null jika user belum login auth
return null;
}
}
// Fungsi untuk verifikasi nomber hp agar mendapatkan OTP
Future<void> verifyPhone(
BuildContext context,
String phoneToSend,
) async {
try {
// ignore: prefer_function_declarations_over_variables
final PhoneCodeSent smsOTPSent = (String verId, int? forceCodeResend) {
verificationId = verId;
};
// Fungsi bawaan auth_firebase untuk verifikasi nomor telpon
await auth.verifyPhoneNumber(
// Nomber yang ingin di verifikasi
phoneNumber: phoneToSend,
// CodeSent
codeSent: smsOTPSent,
// Waktu timeout fungsi
timeout: const Duration(seconds: 10),
// Mengirim SMS dengan kode 6 digit ke nomor telepon yang ditentukan, atau memasukkan pengguna dan [verifikasiCompleted] dipanggil.
codeAutoRetrievalTimeout: (String verId) {
verificationId = verId;
if (verificationId != null) {
notifyListeners();
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (_) {
return VerificationScreen(
beUserPhone: phoneToSend,
);
},
),
);
} else {
Navigator.of(context).pop();
notifyListeners();
}
},
// Memulai proses verifikasi nomor telepon untuk nomor telepon yang diberikan.
verificationCompleted: (PhoneAuthCredential phoneAuthCredential) {
if (verificationId != null) {
notifyListeners();
Future.delayed(const Duration(seconds: 1), () {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (_) {
return VerificationScreen(
beUserPhone: phoneToSend,
);
},
),
);
});
} else {
Navigator.of(context).pop();
notifyListeners();
}
},
// Ketika verifikasi gagal
verificationFailed: (FirebaseAuthException e) {
Navigator.of(context).pop();
if (e.code == 'invalid-phone-number') {
debugPrint('Nomor yang dimasukan salah.');
AppWidget.showSnackBar(
context: context,
content: const Text('Nomor yang anda masukan salah.'),
duration: const Duration(seconds: 3),
);
} else if (e.code == 'too-many-requests') {
debugPrint('Terlalu banyak request');
AppWidget.showSnackBar(
context: context,
content: const Text(
'Anda terlalu sering request OTP, tunggu beberapa saat lalu coba lagi.',
),
duration: const Duration(seconds: 3),
);
} else {
debugPrint('Error Message : $e');
}
});
} catch (e) {
Navigator.of(context).pop();
debugPrint('Catch Error Message : $e');
}
}
// Fungsi untuk verifikasi kode OTP
Future<void> verifyOTP(BuildContext context, String phone, String otp) async {
AppWidget.loadingPageIndicator(context: context);
phoneNumberController.clear();
otpCodeController.clear();
AuthCredential credential;
UserCredential authLogin;
User? currentUser;
try {
// Dari verify id dan otp code di simpan dalam credential
credential = PhoneAuthProvider.credential(
verificationId: verificationId!,
smsCode: otp,
);
// Lakukan login dengan credential lalu simpan ke dalam User Credential
authLogin = await auth.signInWithCredential(
credential,
);
// Simpan User Credential ke dalam model user auth untuk di gunakan
currentUser = authLogin.user;
} catch (e) {
debugPrint('$e');
if (e.toString().contains("firebase_auth/invalid-verification-code")) {
AppWidget.showSnackBar(
context: context,
content: const Text(
'Kode OTP salah, silahkan coba kirim ulang kode dan coba lagi.'),
duration: const Duration(seconds: 3),
);
}
}
// Lakukan pengecekan untuk melanjutkan ke home jika user auth sudah terisi
if (currentUser != null) {
await PhoneAuthDatabase()
.getUserData(currentUser.uid)
.then((userDataDoc) async {
final UserModel userModel = UserModel(
docUID: currentUser!.uid,
phone: phone,
status: 'active',
logedInDate: DateTime.now(),
);
if (userDataDoc.exists) {
await PhoneAuthDatabase().updateDataUser(
currentUser.uid,
data: {
'logedInDate': DateTime.now(),
},
);
} else {
await PhoneAuthDatabase().createUser(userModel);
}
});
// Jika auth login telah terisi maka alihkan kembali ke halaman auth checker
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) {
return const AuthChecker();
},
),
);
}
// Jika user auth tidak terisi maka jangan lanjutkan ke home
else {
Navigator.of(context).pop();
notifyListeners();
}
}
// Fungsi untuk log out
Future<String> logOut(BuildContext context) async {
AppWidget.loadingPageIndicator(context: context);
// Destroy isi model user auth (untuk menandakan bahwa user telah logout)
await auth.signOut();
// Navigasi ke halaman pengecekan
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) {
return const AuthChecker();
},
),
);
notifyListeners();
return 'logout';
}
}
Lalu buka lagi file login.dart kita tambahkan code untuk submit menjadi seperti berikut,
import 'package:country_list_pick/country_list_pick.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
import 'package:snippetphoneauth/widgets/app_widget.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => exit(0),
child: Scaffold(
...
floatingActionButton: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return FloatingActionButton(
backgroundColor:
authProvider.phoneNumberController.text.length > 8
? Colors.blue
: Colors.grey[400],
onPressed: () {
if (authProvider.phoneNumberController.text.trim()[0] == '0') {
_onSubmit(
context,
'${authProvider.countryCode.dialCode!}${authProvider.phoneNumberController.text.trim().substring(1, authProvider.phoneNumberController.text.trim().length).trim()}',
);
} else {
_onSubmit(
context,
'${authProvider.countryCode.dialCode!}${authProvider.phoneNumberController.text.trim()}',
);
}
},
child: const Icon(
Icons.arrow_forward,
),
);
},
),
),
);
}
// Fungsi onsubmit untuk menjalankan verifikasi phone dan melanjutkan ke verifi OTP
Future<void> _onSubmit(
BuildContext context,
String phone,
) async {
debugPrint('phone : $phone');
if (phone.length > 8) {
AppWidget.loadingPageIndicator(context: context);
Provider.of<AuthProvider>(
context,
listen: false,
).verifyPhone(context, phone);
}
}
}
Selanjutnya kita juga perlu menambahkan fungsi ke verification.dart menjadi seperti berikut,
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
import 'package:snippetphoneauth/widgets/app_widget.dart';
class VerificationScreen extends StatelessWidget {
final String beUserPhone;
const VerificationScreen({
Key? key,
required this.beUserPhone,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
...
floatingActionButton: Consumer<AuthProvider>(
builder: (_, AuthProvider authProvider, __) {
return FloatingActionButton(
backgroundColor: authProvider.otpCodeController.text.length > 5
? Colors.blue
: Colors.grey[400],
onPressed: () {
_onSubmit(
context,
beUserPhone,
authProvider.otpCodeController.text.trim(),
);
},
child: const Icon(
Icons.arrow_forward,
),
);
},
),
);
}
// Fungsi onsubmit untuk menjalankan verifikasi OTP
void _onSubmit(BuildContext context, String phone, String otpCode) {
if (otpCode.length > 5) {
// Memanggil fungsi verify otp number di auth provider
Provider.of<AuthProvider>(context, listen: false).verifyOTP(
context,
phone,
otpCode,
);
} else {
AppWidget.showSnackBar(
context: context,
content: const Text('Kode OTP yang dimasukkan kurang.'),
duration: const Duration(seconds: 3),
);
}
}
}
Sampai di sini bisa kita coba melakukan login kurang lebih akan menjadi seperti ini,
Sistem Baca Data User Authentication
Terakhir adalah bagaimana caranya kita mengambil data user authenticationnya, disini saya membuat sistem sendiri dengan menggunakan StreamProvider.
Karena kita sudah mempunyai App State dari AuthProvider dan di dalamnya ada fungsi stream getter user ke collection 'snippet-phone-auth' dan langsung di return ke dalam model UserModel maka nantinya kita tinggal menyimpan variable yang diisi dengan Provider.of<UserModel>(context); dan memanggilnya di manapun. Berikut implementasinya,
Pertama kita perlu membungkus halaman yang mau membaca user model dengan StreamProvider disini saya akan coba mengimplementasikannya di halaman HomeScreen().
Karena HomeScreen() dipanggil di AuthChecker() maka kita akan menambahkan kode menjadi seperti berikut,
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/models/user_model.dart';
import 'package:snippetphoneauth/screens/auth/login.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
import 'package:snippetphoneauth/screens/home.dart';
// Auth status pengecekan
class AuthChecker extends StatelessWidget {
const AuthChecker({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer(builder: (_, AuthProvider authProvider, __) {
// Pengecekan variable auth status untuk menentukan route ke halaman
if (authProvider.auth.currentUser != null) {
return StreamProvider<UserModel>.value(
value: AuthProvider().user,
initialData: UserModel.initialData,
child: const HomeScreen(),
);
} else {
// masuk ke halaman login jika user auth tidak terisi
return const LoginScreen();
}
});
}
}
Untuk pemanggilan UserModel di HomeScreen sebagai berikut, oh iya kita tambahkan juga fungsi untuk logoutnya.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:snippetphoneauth/models/user_model.dart';
import 'package:snippetphoneauth/screens/auth/providers/auth_provider.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final UserModel userModel = Provider.of<UserModel>(context);
return Scaffold(
appBar: AppBar(
title: const Text('Snippet Phone Auth'),
actions: [
// fungsi logout
Consumer<AuthProvider>(
builder: (_, authProvider, __) {
return IconButton(
onPressed: () {
authProvider.logOut(context);
},
icon: const Icon(
Icons.logout,
),
);
},
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// pemanggilan userModel secara realtime
Text(userModel.docUID),
Text(userModel.phone),
Text(userModel.status),
Text(userModel.logedInDate.toString()),
const Text('Home Screen'),
],
),
);
}
}
Selesai! Silahkan coba jalankan kembali projectnya.
Link github https://github.com/sendiagustian/ follow dulu ya😬 dan cari repository snippet-phone-auth.
Demikian lah yang bisa saya sampaikan semoga tutorial dan cara yang saya gunakan ini bisa bermanfaat, apabila ada pertanyaan silahkan tulis di kolom komentar ya.
See you next post! 😉
1 komentar untuk "Flutter Phone Authentication and System Read User Data With Provider"