Aliffia Humairah - 2311531004
Navigation dan Routing Flutter
Navigation atau Navigasi merupakan sebuah proses berpindah dari satu halaman (screen/page)
ke halaman lain dalam sebuah aplikasi flutter. Misalnya dari halaman login ke halaman utama
atau dari halaman produk ke halaman detail produk. Navigasi pada flutter menggunakan widget
Navigator yang bekerja dengan konsep stack (tumpukan).
Gambar: Konsep Dasar Navigasi Flutter
Routing atau Rute adalah sebuah sistem yang digunakan untuk mendefinisikan dan mengelola routes dalam aplikasi, setiap route didefinisikan sehingga Ketika akan memanggil halaman cukup dengan memanggil nama route tersebut, hal ini mempermudah dalam mengelola route tanpa harus membuat instance baru setiap kali akan memanggil suatu halaman.
Jenis Routing pada Flutter:
Widget Navigation menampilkan halaman dengan konsep tumpukan menggunakan animasi transisi Ketika berpindah ke halaman, untuk berpindah ke halaman baru diakses melalui BuildContext dengan memanggil method seperti push() atau pop() secara langsung.
Push()
Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ProductDetail()),
);
},
child: const Text('Go to Product Detail'),
),
),
Pop()
Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Product'),
),
),
Named Routes atau Rute Bernama mengelola route pada widget MateriapApp atau CupertinoApp kemudian memanggilnya berdasarkan nama yang telah diberikan, berikut contoh penggunaan named routes pada Flutter.
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const Product(),
'/product_detail': (context) => const ProductDetail(),
},
);
onPressed: () {
Navigator.pushNamed(context, '/product_detail');
},
Generated Routes yang sebuah mekanisme mengelola routes dengan mengirimkan paremeter dan handle error.
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as Map;
return MaterialPageRoute(
'builder: (context) => DetailPage(data: args['data']),
);
}
return MaterialPageRoute(builder: (context) => NotFoundPage());
},
);
Mekanisme pengelolaan routes ini digunakan jika aplikasi yang membutuhkan route yang komplek, biasanya digunakan Ketika membuat aplikasi web pada flutter.
child: const Text('Open second screen'),
onPressed: () => context.go('/second'),
Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ProductDetail()),
);
},
child: const Text('Go to Product Detail'),
),
);
Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Product'),
),
);
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const Product(),
'/product_detail': (context) => const ProductDetail(),
},
);
Navigator.pushNamed(context, '/product_detail');
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as Map;
return MaterialPageRoute(
builder: (context) => DetailPage(data: args['data']),
);
}
return MaterialPageRoute(builder: (context) => NotFoundPage());
},
);
Mekanisme pengelolaan routes ini digunakan jika aplikasi yang membutuhkan route yang komplek, biasanya digunakan Ketika membuat aplikasi web pada flutter.
child: const Text('Open second screen'),
onPressed: () => context.go('/second'),
// Push
Navigator.push(context, route);
// Pop
Navigator.pop(context);
// Pop dengan data
Navigator.pop(context, 'data yang dikembalikan');
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false,
);
Mengirim Data:
// Dengan constructor
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(id: 123, name: Masnoer),
),
);
// Dengan named routes
Navigator.pushNamed(
context,
'/detail',
arguments: {'id': 123, 'name': 'Masnoer'},
);
Multiple Screen
MyNav.Product dan ProductDetail.
import 'package:flutter/material.dart';
void main() => runApp(const MyNav());
class MyNav extends StatelessWidget {
const MyNav({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const Product(),
'/product_detail': (context) => const ProductDetail(),
},
);
}
}
class Product extends StatelessWidget {
const Product({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Product')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/product_detail');
},
child: const Text('Go to Product Detail'),
),
),
);
}
}
class ProductDetail extends StatelessWidget {
const ProductDetail({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Product Detail')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Product'),
),
),
);
}
}
Buat class HomePage, MyProfile, dan MyProduct.
Pada HomePage kirim data ID & Name ke MyProfile menggunakan constructor.
Pada HomePage kirim data ID & Name ke MyProduct menggunakan named routes.
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const MyProfile(id: 1, name: 'Masnoer'),
),
);
},
child: const Text('Profile'),
);
class MyProfile extends StatelessWidget {
final int id;
final String name;
const MyProfile({super.key, required this.id, required this.name});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('ID: $id'),
Text('Name: $name'),
],
),
),
);
}
}
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/product',
arguments: {'id': 101, 'name': 'Laptop'},
);
},
child: const Text('Product'),
);
class MyProduct extends StatelessWidget {
const MyProduct({super.key});
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as Map?;
final int id = args?['id'] ?? 0;
final String name = args?['name'] ?? 'Unknown';
return Scaffold(
appBar: AppBar(title: const Text('Product')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Product ID: $id'),
Text('Product Name: $name'),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'main_screen.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State createState() => _LoginScreenState();
}
class _LoginScreenState extends State {
final _formKey = GlobalKey();
final TextEditingController _usernameCtrl = TextEditingController();
final TextEditingController _passwordCtrl = TextEditingController();
bool _obscurePassword = true;
@override
void dispose() {
_usernameCtrl.dispose();
_passwordCtrl.dispose();
super.dispose();
}
// === VALIDASI USERNAME ===
// Username tidak boleh kosong
String? _validateUsername(String? value) {
if (value == null || value.isEmpty) {
return 'Username tidak boleh kosong';
}
// Tidak boleh mengandung huruf kapital
if (RegExp(r'[A-Z]').hasMatch(value)) {
return 'Username tidak boleh mengandung huruf kapital';
}
// Hanya boleh huruf kecil, angka, dan simbol tertentu
final validPattern = RegExp(r'^[a-z0-9._-]+$');
if (!validPattern.hasMatch(value)) {
return 'Username hanya boleh huruf kecil, angka, dan simbol . _ -';
}
// tidak boleh mengandung spasi
if (value.contains(' ')) {
return 'Username tidak boleh mengandung spasi';
}
return null; // valid
}
// === VALIDASI PASSWORD ===
String? _validatePassword(String? value) {
// password tidak boleh kosong
if (value == null || value.isEmpty) {
return 'Password tidak boleh kosong';
}
// minimal 8 karakter
if (value.length < 8) {
return 'Password minimal 8 karakter';
}
// huruf besar dan kecil
if (!RegExp(r'(?=.*[A-Z])').hasMatch(value)) {
return 'Harus mengandung huruf besar';
}
if (!RegExp(r'(?=.*[a-z])').hasMatch(value)) {
return 'Harus mengandung huruf kecil';
}
// angka
if (!RegExp(r'(?=.*\d)').hasMatch(value)) {
return 'Harus mengandung angka';
}
return null;
}
// === AKSI LOGIN ===
void _handleLogin() {
if (_formKey.currentState!.validate()) {
final username = _usernameCtrl.text;
final password = _passwordCtrl.text;
// Pindah ke halaman utama, tidak bisa kembali ke login
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => MainScreen(username: username, password: password),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Halaman Login'), centerTitle: true),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ===== FIELD USERNAME =====
TextFormField(
// widget TextFormField untuk username
controller: _usernameCtrl,
decoration: const InputDecoration(
labelText: 'Username',
hintText:
'Masukkan username tanpa spasi dan hanya boleh huruf kecil, angka, dan simbol . _ -',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
validator: _validateUsername,
textInputAction: TextInputAction.next,
),
const SizedBox(height: 16),
// ===== FIELD PASSWORD =====
TextFormField(
// widget TextFormField untuk password
controller: _passwordCtrl,
obscureText:
_obscurePassword, // karakter password tidak terlihat
decoration: InputDecoration(
labelText: 'Password',
hintText:
'Minimal 8 karakter, mengandung huruf besar, huruf kecil dan angka ',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword; // tampil/sembunyi
});
},
),
),
validator: _validatePassword,
textInputAction: TextInputAction.done,
),
const SizedBox(height: 25),
// ===== TOMBOL LOGIN =====
// Menggunkaan widget ElevatedButton
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _handleLogin,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
textStyle: const TextStyle(fontSize: 16),
),
child: const Text('Login'),
),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'home_tab.dart';
import 'product_tab.dart';
import 'profile_tab.dart';
import 'calculator_screen.dart';
class MainScreen extends StatefulWidget {
final String username;
final String password;
const MainScreen({super.key, required this.username, required this.password});
@override
State createState() => _MainScreenState();
}
class _MainScreenState extends State {
int _currentIndex = 0;
late final List _tabs;
@override
void initState() {
super.initState();
_tabs = [
HomeTab(username: widget.username),
ProductTab(),
ProfileTab(username: widget.username),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Aplikasi Praktikum')),
drawer: Drawer(
child: ListView(
children: [
UserAccountsDrawerHeader(
accountName: Text(widget.username),
accountEmail: Text('${widget.username}@gmail.com'),
currentAccountPicture: const CircleAvatar(
child: Icon(Icons.person),
),
),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('Logout (kembali ke Login)'),
onTap: () {
// Kembalikan ke Login dengan mengganti seluruh stack
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (c) => const Placeholder(),
), // gunakan navigator root login di implementasi nyata
(route) => false,
);
},
),
],
),
),
body: _tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_bag),
label: 'Product',
),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
onTap: (i) => setState(() => _currentIndex = i),
),
);
}
}
Akses Tugas disini.