sobatcoding.com - Tutorial cara membuat tour guide aplikasi menggunakan tutorial coach mark pada flutter
Pernahkah kalian menjumpai aplikasi android dimana saat kita masuk ke aplikasi tersebut muncul sorotan atau highlight ke salah satu menu sehingga kita tahu fungsi dari menu-menu tersebut? Bagaimana cara membuatnya?
Artikel kali ini kita akan mencoba belajar implementasi fitur seperti apps tour mengenai fitur atau menu menu apa yang ada di aplikasi andorid yang kita bangun. Fungsinya adalah meperkenalkan kepada user baru tentang menu dan keterangan atau cara penggunaan menu tersebut.
Pada contoh kali ini, admin menggunakan flutter versi 3.10.6. Adapun dependencies yang digunakan adalah iconsax, shared_preferences dan tutorial_coach_mark
Package yang digunakan adalah package tutorial_coah_mark dan kalian bisa install dengan cara command berikut
flutter pub add iconsax
flutter pub add tutorial_coach_mark
flutter pub add shared_preferences
Tambahkan asset image yang kalian punya, kemudian buka file pubpsec.yaml dan setting seperti ini
# To add assets to your application, add an assets section, like this:
assets:
- assets/img/
# - images/a_dot_ham.jpeg
Fungsinya adalah semua asset image yang ada di folder assets/img nanti bisa kita akses di dalam widget
Untuk implementasi pertama kita buat dahulu halaman dua buah halaman. Halaman SplashScreen dan halaman utama yang akan kita jadikan sebagai contoh.
Untuk halaman SplashScreen sederhana kalian bisa buat seperti contoh berikut
import 'package:flutter/material.dart';
import 'package:flutter_tour/home_screen.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
goTohomePage();
}
@override
void dispose() {
super.dispose();
}
void goTohomePage() async {
await Future.delayed(const Duration(seconds: 5), () async {
final navigator = Navigator.of(context);
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.all(30),
height: double.maxFinite,
width: double.maxFinite,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"assets/img/logo.png",
width: 64,
),
const SizedBox(
height: 10,
),
const Text("sobatcoding.com", style: TextStyle(fontWeight: FontWeight.w600),),
const SizedBox(height: 20),
Container(
margin: const EdgeInsets.all(0),
height: 40,
child: const Center(
child: CircularProgressIndicator(
color: Colors.grey,
),
),
),
const SizedBox(height: 10),
const Text("Please wait ..."),
],
)),
),
);
}
}
Kalau kalian lihat dalam kode ini
void goTohomePage() async {
await Future.delayed(const Duration(seconds: 5), () async {
final navigator = Navigator.of(context);
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
});
}
Artinya user akan berada di halaman SplashScreen bertahan atau delay selama 5 detik dan 5 setelahnya, navigasi akan menuju halaman utama HomePage
Selanjutnya buatlah halaman utama berisi header, produk dan bottom navigation dengan tampilan seperti berikut
import 'package:flutter/material.dart';
import 'package:iconsax/iconsax.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<dynamic> items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Widget header() {
return Container(
width: double.infinity,
padding: const EdgeInsets.only(left: 20, top: 10, right: 20, bottom: 10),
color: Colors.green.shade400,
child: Row(
key: profileKey,
mainAxisAlignment: MainAxisAlignment.start,
children: const [
CircleAvatar(
radius: 24,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 20,
backgroundImage: AssetImage("assets/img/logo.png"),
)),
SizedBox(width: 10),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Administrator",
style: TextStyle(fontWeight: FontWeight.w600),
),
Text(
"admin@sobatcoding.com",
style: TextStyle(fontSize: 12, color: Colors.white),
)
],
)
],
),
);
}
Widget itemProduk(i) {
return InkWell(
onTap: () {
null;
},
child: Card(
key: i == 1 ? productKey : null,
elevation: 3.0,
color: Colors.white,
surfaceTintColor: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset("assets/img/shoe.png"),
),
const SizedBox(height: 5),
Expanded(
child: Text(
"Produk $i",
style:
const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
)
],
),
),
);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: double.infinity,
color: Colors.grey.shade50,
child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
header(),
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
children: items.map((e) => itemProduk(e)).toList())
],
),
),
),
),
]),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: 0,
selectedItemColor: Colors.green.shade400,
selectedLabelStyle: const TextStyle(fontSize: 14),
unselectedLabelStyle: const TextStyle(fontSize: 12),
backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Container(
margin: const EdgeInsets.only(
top: 10,
bottom: 10,
),
child: const Icon(Iconsax.direct_normal),
),
label: 'Beranda',
),
BottomNavigationBarItem(
icon: Container(
margin: const EdgeInsets.only(
top: 10,
bottom: 10,
),
child: const Icon(Iconsax.bag),
),
label: 'Keranjang',
),
BottomNavigationBarItem(
icon: Container(
margin: const EdgeInsets.only(
top: 10,
bottom: 10,
),
child: const Icon(Iconsax.profile_circle),
),
label: 'Profile',
),
]),
);
}
}
Buka file main.dart , arahkan untuk default route ke halaman SplashScreen
return MaterialApp(
title: 'Tour Guide Flutter Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const SplashScreen(),
);
Buatlah sebuah widget bernama CoachMarkDescription dan masukkan kode berikut
import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
class CoachMarkDescription extends StatefulWidget {
const CoachMarkDescription(
{super.key,
required this.tutorialCoachMark,
required this.text});
final String text;
final TutorialCoachMark tutorialCoachMark;
@override
State<CoachMarkDescription> createState() => _CoachMarkDescriptionState();
}
class _CoachMarkDescriptionState extends State<CoachMarkDescription> {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10)),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text(
widget.text,
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(height: 16),
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
widget.tutorialCoachMark.skip();
},
style: TextButton.styleFrom(
foregroundColor: Colors.lightBlue.shade700,
backgroundColor: Colors.white,
elevation: 2.0),
child: const Text("SKIP")),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
widget.tutorialCoachMark.next();
},
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.lightBlue.shade700,
),
child: const Text("NEXT"),
)
],
),
)
],
),
);
}
}
Selanjutnya kita buat Global Key yang digunakan untuk indexing widget mana yang akan kita highlight.
GlobalKey profileKey = GlobalKey();
GlobalKey productKey = GlobalKey();
GlobalKey chartKey = GlobalKey();
Kita buat contoh ada 3 buah key masing-masing bernama profileKey, productKey dan chartKety.
Selanjutnya buatlah function untuk generate Tutorial
void showTutorial() {
initTargets();
tutorialCoachMark = TutorialCoachMark(
targets: targets,
pulseEnable: false,
onFinish: () {
//
})
..show(context: context);
}
void initTargets() {
targets = [
TargetFocus(
identify: "Profile",
keyTarget: profileKey,
enableOverlayTab: true,
shape: ShapeLightFocus.RRect,
contents: [
TargetContent(
align: ContentAlign.bottom,
builder: (context, controller) {
return CoachMarkDescription(
tutorialCoachMark: tutorialCoachMark!,
text: "Ini adalah informasi profile Anda");
})
]),
TargetFocus(
identify: "Product",
keyTarget: productKey,
enableOverlayTab: true,
shape: ShapeLightFocus.RRect,
contents: [
TargetContent(
align: ContentAlign.bottom,
builder: (context, controller) {
return CoachMarkDescription(
tutorialCoachMark: tutorialCoachMark!,
text:
"Pilih produk sepatu yang Anda inginkan dengan sekali klik");
})
]),
TargetFocus(
identify: "Chart",
keyTarget: chartKey,
enableOverlayTab: true,
shape: ShapeLightFocus.Circle,
contents: [
TargetContent(
align: ContentAlign.top,
builder: (context, controller) {
return CoachMarkDescription(
tutorialCoachMark: tutorialCoachMark!,
text:
"Semua paket yang kamu tambahkan ada di dalam keranjang ini dan siap untuk di cekout");
})
]),
];
}
Kita set untuk key diatas ke dalam beberapa widget yang tersedia
untuk header kita set key profileKey di dalam widget Row
Widget header() {
return Container(
width: double.infinity,
padding: const EdgeInsets.only(left: 20, top: 10, right: 20, bottom: 10),
color: Colors.green.shade400,
child: Row(
key: profileKey,
...
}
untuk produk kita set key productKey di dalam widget Card elemen pertama
Widget itemProduk(i) {
return InkWell(
onTap: () {
null;
},
child: Card(
key: i == 1 ? productKey : null,
...
);
}
untuk bottomNavigationBar kita set key chartKey di dalam salah satu item widget BottomNavigationBar
bottomNavigationBar: BottomNavigationBar(
...
BottomNavigationBarItem(
icon: Container(
key: chartKey,
margin: const EdgeInsets.only(
top: 10,
bottom: 10,
),
child: const Icon(Iconsax.bag),
),
label: 'Keranjang',
),
...
])
Langsung kita jalankan hasilnya kurang lebih seperti berikut
Caranya sangat mudah kita tinggal tambahkan semacam session yang kita simpan saat tour apps selesai
Kita buat file bernama CoachMarkPreferences.dart dan masukkan kode berikut
import 'package:shared_preferences/shared_preferences.dart';
class CoachMarkPreferences {
//fungsi untuk menyimpan session tour
static Future<void> saveSession() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
pref.setBool("watchedTour", true);
}
//fungsi untuk mengecek apakah session tour sudah disimpan
static Future<bool> watchedTour() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
bool watchedTour = pref.getBool("watchedTour") ?? false;
return watchedTour;
}
}
Kemudian implementasikan di halaman widget homePage, dibagian initState kita modifikasi seperti berikut ini
@override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 1), () async {
//kita cek apakah ada session yang sudah pernah disimpan atau belum
bool tour = await CoachMarkPreferences.watchedTour();
//jika tidak ada apps tour akan muncul
if (!tour) {
showTutorial();
}
});
}
Kemudian di fungsi showTutorial kita masukkan fungsi simpan session di dalam event onFinish:
void showTutorial() {
initTargets();
tutorialCoachMark = TutorialCoachMark(
targets: targets,
pulseEnable: false,
onFinish: () {
//kita tambahakan fungsi simpan session saat tour app berakhir atau finish
CoachMarkPreferences.saveSession();
})
..show(context: context);
}
Demikian tutorial kali ini semoga bermanfaat.
Komentar 0