Kelas 10 • Semester 1 • Buku Digital Interaktif
Lanjutan dari BAB 1 (Pengenalan Mobile Dev) & BAB 2 (Dasar Dart)
Klik topik mana pun untuk langsung melompat ke materinya.
BAB 1 mengenalkan dunia mobile. BAB 2 mengajarkan dasar Dart (variabel, tipe data, fungsi). BAB 3 menggabungkannya: kita mulai berpikir dalam bentuk objek — cara berpikir yang dipakai Flutter di setiap widget.
Class adalah cetakan atau rancangan untuk membuat sesuatu. Misalnya cetakan rumah.
Dalam sebuah class biasanya terdapat dua hal utama:
nama, usiaperkenalan(), belajar()Class hanyalah rancangan. Selama belum dibuat menjadi objek, class belum bisa digunakan secara langsung (bukan benda yang nyata).
class Mahasiswa {
String nama = "";
int usia = 0;
void perkenalan() {
print("Halo");
}
}
Variabel yang ada di dalam class disebut Property. Fungsinya menyimpan data atau ciri-ciri dari objek.
Mahasiswa: nama dan usia.class Mahasiswa {
// Property (Data)
String nama = "";
int usia = 0;
}
Fungsi yang ada di dalam class disebut Method. Fungsinya melakukan sebuah tindakan atau perilaku.
perkenalan() dan belajar().class Mahasiswa {
String nama = "";
// Method (Aksi)
void perkenalan() {
print("Halo");
}
void belajar() {
print("Sedang belajar...");
}
}
✨ Nah, sekarang blueprint ini kita pakai untuk mencetak objek!
main(), kita memanggil nama class Mahasiswa() untuk mencetak objek.m).class Mahasiswa {
String nama = "";
int usia = 0;
}
void main() {
// Blueprint Mahasiswa dipakai untuk mencetak objek m
var m = Mahasiswa();
}
.) setelah nama objek untuk mengakses property-nya.m.nama = "Budi";print(m.nama);class Mahasiswa {
String nama = "";
int usia = 0;
}
void main() {
var m = Mahasiswa();
// Mengisi property (Data)
m.nama = "Budi";
m.usia = 16;
// Membaca property (Data)
print(m.nama);
print(m.usia);
}
.) diikuti kurung buka-tutup ().m.perkenalan();class Mahasiswa {
String nama = "";
void perkenalan() {
print("Halo, saya $nama");
}
}
void main() {
var m = Mahasiswa();
m.nama = "Budi";
// Memanggil method (Aksi)
m.perkenalan();
}
Tanpa constructor, data harus diisi satu per satu secara manual setelah object dibuat.
class Mahasiswa {
String nama = "";
int usia = 0;
}
void main() {
var mhs = Mahasiswa(); // Objek dibuat dulu
mhs.nama = "Sari"; // Harus diisi 1 per 1
mhs.usia = 16; // Merepotkan jika data banyak!
}
Constructor membuat pengisian data menjadi jauh lebih cepat & praktis (langsung saat object dibuat).
class Mahasiswa {
String nama;
int usia;
// Constructor
Mahasiswa(this.nama, this.usia);
}
void main() {
// Data LANGSUNG diisi saat objek dicetak!
var mhs = Mahasiswa("Sari", 16);
}
this secara Sederhana
Perhatikan baris sintaks constructor berikut: Mahasiswa(this.nama, this.usia);
this?Saat kita memanggil:
var mhs = Mahasiswa("Sari", 16);
Mahasiswa() dipanggil!class Mahasiswa {
String nama;
int usia;
// Constructor
Mahasiswa(this.nama, this.usia);
}
void main() {
// Constructor dipanggil di sini
var mhs = Mahasiswa('Sari', 16);
print('${mhs.nama}, ${mhs.usia} tahun');
}
💡 OOP adalah cara membuat program dengan konsep objek.
Data (Ciri):
Aksi (Perilaku):
Property (Data):
String warna;String merk;Method (Aksi):
void jalan()void rem()Data dan fungsi tersebar secara terpisah di mana-mana.
Dampaknya Sulit:
Data + aksi dikumpulkan rapat dalam satu Object.
Keuntungannya Lebih:
Dalam Object Oriented Programming, ada 4 konsep utama (pilar) yang melindungi dan menstrukturkan kode kita:
🔒 Melindungi data
Menjaga data agar tidak bisa diubah sembarangan dari luar.
🧬 Pewarisan sifat
Mewariskan atribut & aksi dari class induk ke class anak.
🎭 Banyak bentuk
Satu nama method, namun memiliki perilaku berbeda-beda.
⚙️ Menyembunyikan detail
Menyembunyikan kerumitan mesin di balik antarmuka sederhana.
Saldo bisa diubah sembarangan jadi minus. Ini tidak masuk akal!
class Dompet {
double saldo = 0;
}
void main() {
var d = Dompet();
// Saldo diubah sembarangan dari luar!
d.saldo = -50000;
}
Gunakan _ agar private, lalu ubah lewat method dengan validasi aturan.
class Dompet {
double _saldo = 0; // Private (dikunci 🔒)
void setor(double jumlah) {
if (jumlah > 0) { // Aturan aman
_saldo += jumlah;
}
}
double get saldo => _saldo;
}
_) ➜ Akses Hanya Lewat Aturan (setor())
Orang luar tidak boleh mengambil/mengubah uang langsung di dompetmu. Harus melalui aturan!
Setelah _saldo dikunci dengan _, bagaimana cara kita membaca nilainya dari luar class?
var dompet = Dompet();
// ❌ INI DILARANG! Tidak bisa akses dari luar.
print(dompet._saldo);
// Error: '_saldo' isn't defined for the class
Getter adalah pintu baca yang kita buat secara sengaja untuk mengizinkan pembacaan data private.
class Dompet {
double _saldo = 0; // Private 🔒
// Cara ringkas (arrow)
double get saldo => _saldo;
// Cara blok (sama artinya)
double get saldo {
return _saldo;
}
}
void main() {
var dompet = Dompet();
dompet.setor(50000);
// ✅ Akses via getter — seperti variabel biasa!
print(dompet.saldo);
}
double get saldo => _saldo; | Izin BACA saja, tidak bisa ubah
Pengguna cukup tulis dompet.saldo — terasa seperti property biasa, padahal ada perlindungan di baliknya.
Setter adalah pintu tulis yang memungkinkan kita mengubah nilai private variable — tetapi hanya jika memenuhi aturan/validasi yang sudah kita tetapkan.
class Dompet {
double _saldo = 0;
// Getter (baca)
double get saldo => _saldo;
// Setter (tulis + validasi)
set saldo(double jumlah) {
if (jumlah >= 0) {
_saldo = jumlah; // ✅ aman, diterima
} else {
print('Saldo tidak boleh negatif'); // ❌ ditolak
}
}
}
void main() {
var dompet = Dompet();
// ✅ Saldo valid — diterima
dompet.saldo = 100000;
print(dompet.saldo);
// ❌ Saldo negatif — ditolak setter
dompet.saldo = -50000;
}
| Materi | Status |
|---|---|
| Class & Object | ✅ Wajib |
| Constructor | ✅ Wajib |
Private Variable _ | ✅ Wajib |
| Method | ✅ Wajib |
| Getter | ✅ Wajib |
| Setter | ✅ Wajib |
| Validasi Setter | ✅ Wajib |
// Versi method biasa
double getSaldo() { return _saldo; }
void setSaldo(double val) { _saldo = val; }
// Cara pakainya:
print(dompet.getSaldo()); // perlu ()
dompet.setSaldo(50000); // tidak intuitif
// Versi getter/setter Dart
double get saldo => _saldo;
set saldo(double val) { _saldo = val; }
// Cara pakainya:
print(dompet.saldo); // seperti property biasa!
dompet.saldo = 50000; // intuitif & natural
Gen 🧬 mengalir turun. Mahasiswa & Dosen mewarisi sifat dari Orang.
Parent Class
= kelas induk
Child Class
= kelas turunan
extends
= mewarisi
super() untuk menginisialisasi data parent.
extendsclass Mahasiswa extends Orang
super() dan Method Warisansuper(nama)?Constructor tidak diwariskan. Child class perlu cara untuk mengisi data parent — caranya: panggil super()!
Mahasiswa(String nama, this.nim)
: super(nama);
m.sapa() Bisa Dipanggil?Mahasiswa tidak mendefinisikan sapa() — tapi kenapa bisa dipanggil?
class Orang {
String nama;
Orang(this.nama);
void sapa() => print('Halo, saya $nama');
}
class Mahasiswa extends Orang {
String nim;
Mahasiswa(String nama, this.nim)
: super(nama);
}
void main() {
var m = Mahasiswa('Budi', '2024001');
m.sapa(); // method warisan dari Orang
}
Ketika objek m dibuat, di dalam memori tersimpan 2 data:
Setiap class harus mendefinisikan hal yang sama berulang-ulang:
Definisikan sekali di parent, sisanya tinggal extends:
Inheritance bukan berarti menyalin kode.
Yang terjadi adalah: child class memperoleh akses terhadap property dan method dari parent class.
💡 Kode class Orang tetap ada di satu tempat. Mahasiswa dan Dosen hanya mendapat hak akses — bukan salinan kodenya.
main() Sampai OutputMahasiswa("Budi", "2024001") dipanggil
Mahasiswa mulai berjalan
super(nama) → constructor Orang dipanggil
nama = "Budi" tersimpan (dari Orang)
nim = "2024001" tersimpan (milik Mahasiswa)
m selesai dibuat 🎉
m.sapa() dipanggil dari main()
sapa() milik Orang dijalankan
class Orang {
String nama;
Orang(this.nama); // ← step 4 & 5
void sapa() => // ← step 9
print('Halo, saya $nama');
}
class Mahasiswa extends Orang {
String nim;
Mahasiswa(String nama, this.nim) // ← step 3
: super(nama); // ← step 4
}
void main() { // ← step 1
var m = Mahasiswa( // ← step 2
'Budi', '2024001'
); // ← step 7
m.sapa(); // ← step 8
}
super() selalu jalan sebelum body constructor child — memastikan data parent sudah siap lebih dahulu!Method bersuara() sama, tetapi hasilnya berbeda untuk tiap objek. Satu nama method, banyak bentuk perilaku.
@override menulis ulang method milik parent.class Hewan {
void bersuara() => print('...');
}
class Anjing extends Hewan {
@override
void bersuara() => print('Guk guk!');
}
class Kucing extends Hewan {
@override
void bersuara() => print('Meong!');
}
void main() {
List<Hewan> hewan = [Anjing(), Kucing()];
for (var h in hewan) h.bersuara(); // method sama, hasil beda
}
"Apakah void bersuara() itu getter atau function?"
Ini adalah Method (fungsi yang berada di dalam class).
())
())
| Jenis | Deklarasi | Pemanggilan |
|---|---|---|
| Method | void bersuara() | h.bersuara(); (pakai ()) |
| Getter | String get nama | orang.nama; (tanpa ()) |
// Contoh Getter vs Method:
class Orang {
String _nama = "Rifqi";
String get nama => _nama; // Getter (tanpa ())
}
// Pemanggilan:
print(orang.nama); // Getter: tanpa ()
orang.bersuara(); // Method: pakai ()
Kita mulai dari membuat class induk bernama Hewan sebagai standar perilaku dasar.
class Hewan {
// Method dasar bersuara
void bersuara() => print('...');
}
class Hewan {
void bersuara() {
print("...");
}
}
Tanda => (Arrow Function) hanyalah penulisan singkat untuk fungsi yang cuma berisi 1 baris kode.
@override 🐶🐱class Anjing extends Hewan {
@override
void bersuara() => print("Guk guk!");
}
class Kucing extends Hewan {
@override
void bersuara() => print("Meong!");
}
@override?@override berarti: "Saya tidak membuat method baru, melainkan MENIMPA (mengganti) isi method lama milik parent."
print('...')print('Guk guk!')print('Meong!')List<Hewan> 🗃️main()void main() {
// Membuat List bertipe Hewan
List<Hewan> hewan = [
Anjing(),
Kucing()
];
}
List<Hewan>?Yang dibuat bukan List<Anjing>, melainkan List<Hewan>. Kenapa Anjing dan Kucing boleh masuk ke dalam satu List yang sama?
Karena keduanya adalah Hewan, maka wadah bertipe List<Hewan> sah dan berhak menampung objek Anjing maupun Kucing!
for (var h in hewan) {
h.bersuara();
}
for-inLoop mengambil isi List<Hewan> satu per satu secara bergantian dan menyimpannya sementara ke variabel h.
Variabel h menunjuk ke objek Anjing di memori.
Variabel h berpindah menunjuk ke objek Kucing di memori.
h.bersuara()Perhatikan bagaimana program bertindak saat mengeksekusi h.bersuara():
h.bersuara() ➜ sebenarnya Anjing().bersuara()h.bersuara() ➜ sebenarnya Kucing().bersuara()hh = Anjing()Anjing().bersuara() ➜ "Guk guk!"h = Kucing()Kucing().bersuara() ➜ "Meong!"
💡 Karena pemanggilan method-nya persis sama (h.bersuara();), tetapi perilaku / hasilnya berbeda sesuai objek yang sedang ditunjuk oleh h.
| Objek | Method Dipanggil | Output |
|---|---|---|
| Anjing() | bersuara() | Guk guk! |
| Kucing() | bersuara() | Meong! |
"Satu nama method (bersuara()), tetapi memiliki banyak bentuk perilaku tergantung objek yang memanggilnya."
Kamu cukup injak pedal gas — gigi berputar di balik panel tanpa perlu kamu pahami. Itulah abstraction: detail rumit disembunyikan.
abstract class mendefinisikan "kontrak" tanpa detail.abstract class Kendaraan {
void nyalakan(); // kontrak: detail disembunyikan
}
class Mobil extends Kendaraan {
@override
void nyalakan() => print('Mesin mobil menyala (brrm!)');
}
void main() {
Kendaraan k = Mobil();
k.nyalakan(); // user tak perlu tahu detail mesin
}
Semua bermula dari Class. Dari sana lahir Object, dan 4 pilar OOP menempel padanya.
Flutter dibangun sepenuhnya di atas OOP. Setiap Widget adalah sebuah class!
Text, Container, Column — semuanya class.extends StatelessWidget → Inheritance.build() di-@override → Polymorphism.// Widget Flutter adalah sebuah class!
class KartuMahasiswa extends StatelessWidget {
final String nama; // property
const KartuMahasiswa(this.nama);
@override // polymorphism
Widget build(BuildContext context) {
return Text('Mahasiswa: $nama');
}
}
Kita gabungkan semua konsep menjadi satu program nyata.
Baca komentar // di tiap baris untuk memahami fungsinya.
// === Class blueprint untuk satu mahasiswa ===
class Mahasiswa {
String nama; // property: nama
String nim; // property: nomor induk
double _nilai = 0; // private (encapsulation)
Mahasiswa(this.nama, this.nim); // constructor
double get nilai => _nilai; // getter aman
void setNilai(double n) { // setter terkontrol
if (n >= 0 && n <= 100) _nilai = n; // validasi
}
void tampil() { // method
print('$nim | $nama | nilai: $_nilai');
}
}
// === Class pengelola daftar mahasiswa ===
class SistemAkademik {
final List<Mahasiswa> _data = []; // koleksi objek
void tambah(Mahasiswa m) => _data.add(m);
void tampilSemua() {
print('=== DAFTAR MAHASISWA ===');
for (var m in _data) m.tampil(); // polymorphism-ready
}
}
void main() {
var sistem = SistemAkademik();
var a = Mahasiswa('Budi', '24001');
var b = Mahasiswa('Sari', '24002');
a.setNilai(88);
b.setNilai(92);
sistem.tambah(a);
sistem.tambah(b);
sistem.tampilSemua();
}
Beginilah hasil saat program dijalankan di terminal:
Buat sebuah class Buku dengan property judul dan penulis, lalu buat 2 objek buku dan tampilkan datanya.
class Buku dengan dua property.Buku(this.judul, this.penulis).info() yang mencetak judul & penulis.main(), buat 2 objek dan panggil info().Sebuah kebun binatang punya banyak Hewan. Setiap hewan bisa bersuara(), tapi tiap jenis hewan bunyinya berbeda.
Hewan dengan method bersuara().Sapi extends Hewan → override jadi "Moo".Ayam extends Hewan → override jadi "Kukuruyuk".List<Hewan> berisi keduanya, panggil bersuara() dalam loop.Bangun program Sistem Data Siswa versimu sendiri yang dapat menyimpan dan menampilkan beberapa siswa beserta nilainya.
| Kriteria | Poin |
|---|---|
| Struktur class benar | 30 |
| Inheritance tepat | 25 |
| Encapsulation tepat | 25 |
| Program berjalan & output benar | 20 |
⏰ Deadline: ____ / ____ / ______ | Kumpulkan file .dart beserta screenshot output.
Kamu kini berpikir dalam bentuk objek — fondasi setiap aplikasi Flutter.
Widget & Layout Flutter: menerapkan OOP untuk membangun antarmuka aplikasi pertamamu yang bisa dilihat dan disentuh.
"Setiap programmer hebat pernah bingung dengan OOP. Kamu sudah melewatinya. Teruslah membangun!"