• PEMROGRAMAN WEB DINAMIS

    Pengertian web dinamis adalah suatu web yang konten atau isinya dapat berubah-ubah setiap saat. Sebab dalam teknologi pembuatan web dinamis sudah dirancang semudah mungkin bagi pemakai atau user yang menggunakan web tersebut..

  • SIMULASI DAN KOMUNIKASI DIGITAL

    Suatu proses peniruan dalam bentuk visual yang dideskripsikan menyerupai kata, gambar dan grafis..

  • SISTEM KOMPUTER

    Sistem komputer adalah suatu jaringan elektronik yang terdiri dari perangkat lunak dan perangkat keras yang melakukan tugas tertentu (menerima input, memproses input, menyimpan perintah-perintah, dan menyediakan output dalam bentuk informasi). Selain itu dapat pula diartikan sebagai elemen-elemen yang terkait untuk menjalankan suatu aktivitas dengan menggunakan komputer..

  • DASAR DESAIN GRAPIS

    Banyak yang berpikiran kalau desain yang baik adalah yang membutuhkan jam kerja yang banyak, membutuhkan skill tinggi dan aplikasi yang mahal. Ya, memang, tapi sebenarnya desain yang baik adalah desain yang sederhana, yang membuat setiap orang yang melihatnya mudah menangkap maksud dari sebuah bentuk visual tersebut..

  • BASIS DATA

    Pangkalan data atau basis data (bahasa Inggris: database) adalah kumpulan informasi yang disimpan di dalam komputer secara sistematik sehingga dapat diperiksa menggunakan suatu program komputer untuk memperoleh informasi dari basis data tersebut. Perangkat lunak yang digunakan untuk mengelola dan memanggil kueri (query) basis data disebut sistem manajemen basis data (database management system, DBMS). Sistem basis data dipelajari dalam ilmu informasi.

Belajar REST API dengan Flutter: Konsep, Ilustrasi, Praktik, hingga Menghadapi Error

Belajar REST API dengan Flutter: Konsep, Ilustrasi, Praktik, hingga Menghadapi Error

Halo teman-teman, di postingan kali ini saya ingin berbagi pengalaman belajar REST API bersama siswa menggunakan pendekatan yang sederhana namun aplikatif. Materi ini saya angkat dari obrolan dan praktik langsung saat mencoba membuat aplikasi kecil dengan Dart Flutter melalui platform online zapp.run.

Apa itu REST API?

REST API (Representational State Transfer – Application Programming Interface) adalah cara sebuah aplikasi berkomunikasi dengan aplikasi lain melalui internet.
Singkatnya, REST API seperti jembatan penghubung agar aplikasi kita bisa mengambil atau mengirim data ke server.

Metode HTTP yang sering digunakan:

  1. GET → mengambil data dari server.

  2. POST → mengirim data baru ke server.

  3. PUT → memperbarui data yang sudah ada.

  4. DELETE → menghapus data dari server.

Ilustrasi Sederhana: Warung Makan

Agar siswa mudah paham, saya menggunakan analogi warung makan:

  • GET = pembeli meminta daftar menu.

  • POST = pembeli memesan makanan baru.

  • PUT = pembeli mengganti pesanan yang salah.

  • DELETE = pembeli membatalkan pesanan.

Dengan ilustrasi ini, siswa lebih cepat mengingat peran masing-masing metode HTTP.

Mencoba REST API dengan Flutter

Setelah paham konsep, kami lanjut mencoba di Flutter (via zapp.run).
Aplikasi ini sederhana: menampilkan data dari server palsu (dummy API) dan memungkinkan pengguna menambahkan data.

File pubspec.yaml

Kami menambahkan dependency http agar bisa melakukan request ke server.

name: rest_api_demo
description: A simple REST API demo with Flutter
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ">=2.17.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.5

dev_dependencies:
  flutter_test:
    sdk: flutter

File main.dart

Kami menuliskan kode dasar Flutter untuk melakukan request API dan menampilkan data di layar.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'REST API Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: ApiDemoPage(),
    );
  }
}

class ApiDemoPage extends StatefulWidget {
  @override
  _ApiDemoPageState createState() => _ApiDemoPageState();
}

class _ApiDemoPageState extends State<ApiDemoPage> {
  List data = [];

  Future<void> fetchData() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
    if (response.statusCode == 200) {
      setState(() {
        data = json.decode(response.body);
      });
    } else {
      throw Exception('Gagal mengambil data');
    }
  }

  @override
  void initState() {
    super.initState();
    fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("REST API Demo")),
      body: data.isEmpty
          ? Center(child: CircularProgressIndicator())
          : ListView.builder(
              itemCount: data.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(data[index]['title']),
                  subtitle: Text(data[index]['body']),
                );
              },
            ),
    );
  }
}

Menghadapi Error

Namanya belajar coding, tentu ada saja error. Berikut beberapa error yang muncul saat praktik:

  1. Error Null Safety pada Timer

    Error: Method 'cancel' cannot be called on 'Timer?' because it is potentially null.
    Try calling using ?. instead.
    

    ✅ Solusi: tambahkan ? agar sesuai dengan sistem null safety:

    gameTimer?.cancel();
    
  2. Error pada DateTime nullable

    Error: The argument type 'DateTime?' can't be assigned to the parameter type 'DateTime'
    

    ✅ Solusi: gunakan operator ! untuk memastikan data tidak null:

    TimeOfDay.fromDateTime(selectedTime!)
    
  3. Error getter ‘width’ bukan milik double (saat mencoba game lain di Flutter).
    ✅ Solusi: pastikan variabel didefinisikan dengan benar dan bukan tipe double ketika ingin akses .width.

Dengan adanya error ini, siswa belajar debugging: tidak hanya menyalin kode, tetapi juga paham bagaimana memperbaiki masalah sesuai pesan error.

Kesimpulan

Belajar REST API tidak harus rumit. Dengan:

  • Ilustrasi sederhana (seperti warung makan),

  • Praktik langsung di Flutter,

  • Menghadapi error nyata,

siswa bisa memahami alur komunikasi aplikasi dengan server sekaligus mengasah keterampilan problem solving.


✍️ Tulisan ini dibuat berdasarkan pengalaman praktik REST API dengan Flutter di zapp.run, lengkap dengan error-error yang muncul dan solusinya.


mau coba aplikasi yg sudah jadi ?
ikuti link ini : https://z313y06jq313z.zapp.page/#/

Share:

Membuat Desain Halaman Login Mobile dengan Flutter di Web Flutter Zapp.run

Membuat Desain Halaman Login Mobile dengan Flutter di Web Flutter Zapp.run


Pendahuluan

Pada era digital saat ini, hampir semua aplikasi mobile memerlukan fitur login sebagai pintu masuk bagi pengguna. Desain halaman login yang menarik dan responsif akan meningkatkan pengalaman pengguna serta memberikan kesan profesional pada aplikasi yang kita buat.

Dalam artikel ini, kita akan belajar membuat desain halaman login mobile menggunakan Dart Flutter, yang dapat dijalankan langsung di web Flutter Zapp.run tanpa perlu instalasi rumit.


Tujuan Pembelajaran

  1. Memahami cara membuat tampilan halaman login di Flutter.

  2. Menggunakan Widget Flutter seperti Scaffold, AppBar, TextField, dan ElevatedButton.

  3. Mendesain tampilan login agar menarik dan responsif.


Fitur Desain Halaman Login

Tampilan login yang akan kita buat memiliki:

  • AppBar dengan judul Login.

  • Background berwarna hijau untuk memberikan nuansa segar.

  • Lingkaran dengan ikon pengguna di bagian atas form login.

  • Kolom Username dan Password dengan ikon di dalamnya.

  • Tombol Sign In berwarna oranye.

  • Tautan Forgot Password?


Kode Program Lengkap

Berikut adalah kode Dart Flutter untuk membuat desain halaman login seperti contoh:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const LoginPage(),
    );
  }
}

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Login"),
        backgroundColor: Colors.blue,
      ),
      backgroundColor: Colors.teal[600],
      body: Center(
        child: SingleChildScrollView(
          child: Column(
            children: [
              // Lingkaran dengan icon user
              Stack(
                alignment: Alignment.center,
                children: [
                  Container(
                    width: 100,
                    height: 100,
                    decoration: const BoxDecoration(
                      color: Colors.white,
                      shape: BoxShape.circle,
                    ),
                  ),
                  const Icon(
                    Icons.person,
                    size: 60,
                    color: Colors.grey,
                  ),
                ],
              ),
              const SizedBox(height: 20),

              // Card form login
              Container(
                margin: const EdgeInsets.symmetric(horizontal: 24),
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(8),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black12,
                      blurRadius: 6,
                      offset: Offset(0, 2),
                    ),
                  ],
                ),
                child: Column(
                  children: [
                    // Username field
                    TextField(
                      decoration: InputDecoration(
                        prefixIcon: Icon(Icons.person_outline),
                        labelText: 'User Name',
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                        ),
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Password field
                    TextField(
                      obscureText: true,
                      decoration: InputDecoration(
                        prefixIcon: Icon(Icons.lock_outline),
                        suffixIcon: Icon(Icons.visibility),
                        labelText: 'Password',
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                        ),
                      ),
                    ),

                    const SizedBox(height: 8),

                    // Forgot Password link
                    Align(
                      alignment: Alignment.centerRight,
                      child: TextButton(
                        onPressed: () {},
                        child: const Text(
                          "Forgot Password?",
                          style: TextStyle(color: Colors.grey),
                        ),
                      ),
                    ),

                    const SizedBox(height: 8),

                    // Tombol Sign In
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton(
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.orange,
                          padding: const EdgeInsets.symmetric(vertical: 14),
                        ),
                        onPressed: () {},
                        child: const Text(
                          "Sign in",
                          style: TextStyle(color: Colors.white, fontSize: 16),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Cara Menjalankan di Web Flutter Zapp.run

  1. Buka situs https://zapp.run.

  2. Pilih template Flutter.

  3. Hapus kode bawaan dan tempelkan kode di atas.

  4. Klik tombol Run untuk menjalankan program.

  5. Untuk mencoba hasil dari postingan ini ikuti link berikut : 
    https://zy3406gay350.zapp.page/#/


Penutup

Dengan mengikuti tutorial ini, kita telah berhasil membuat halaman login yang menarik menggunakan Flutter. Desain seperti ini dapat menjadi dasar untuk pengembangan fitur autentikasi yang lebih kompleks, seperti validasi input, koneksi ke database, atau integrasi dengan API.

Anda bisa memodifikasi warna, ikon, maupun layout sesuai kebutuhan agar tampilan lebih sesuai dengan tema aplikasi yang sedang dibuat.


Saran pengembangan lanjutan, bisa buat versi dengan animasi fade-in dan smooth transition supaya halaman login lebih hidup untuk diposting di blog Anda.

Share:

Flutter Web REST API — Materi & Proyek (main.dart + panduan)).

 Flutter Web REST API — Materi & Proyek (main.dart + panduan)).

 Aktifitas Pembelajaran:

• Rencana pembelajaran (tujuan, alur 7JP, penilaian, aktivitas nyata)
• Petunjuk singkat cara pakai di zapp.run (tambahkan http: di pubspec.yaml)
• Kode lengkap lib/main.dart — aplikasi ToDo Manager (Flutter Web) yang menunjukkan operasi REST API: GET, POST, PUT, DELETE menggunakan JSONPlaceholder (public demo API).

Beberapa hal yang perlu kamu ketahui langsung:

  • JSONPlaceholder cocok untuk praktik karena menerima POST/PUT/DELETE tetapi tidak menyimpan perubahan permanen — bagus untuk belajar konsep HTTP/REST.

  • Untuk membuat proyek jalan di zapp.run: buat project Flutter Web → tambahkan http: ^0.13.6 di pubspec.yaml → copy-paste main.dart dari canvas → run (web).

  • Aplikasi sudah termasuk: tampil list (GET), tambah item (POST), edit (PUT), hapus (DELETE), toggle completed (PUT), refresh (GET).

Saran Pengembangan:

  • tambahkan versi yang terhubung ke Google Sheets / Firebase (nyata & persistensi), atau

  • ubah resource dari JSONPlaceholder ke backend sederhana (contoh Node/Flask) + skrip quick-start?

Berikut Full Codingan:

import 'dart:convert';

import 'package:flutter/foundation.dart';

import 'package:flutter/material.dart';

import 'package:http/http.dart' as http;


void main() {

  runApp(const MyApp());

}


class MyApp extends StatelessWidget {

  const MyApp({super.key});


  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'REST API Demo - ToDo Manager',

      debugShowCheckedModeBanner: false,

      theme: ThemeData(

        primarySwatch: Colors.indigo,

      ),

      home: const HomePage(),

    );

  }

}


class TodoItem {

  int id;

  String title;

  bool completed;


  TodoItem({required this.id, required this.title, required this.completed});


  factory TodoItem.fromJson(Map<String, dynamic> json) => TodoItem(

        id: json['id'] is int ? json['id'] : int.parse(json['id'].toString()),

        title: json['title'] ?? '',

        completed: json['completed'] ?? false,

      );


  Map<String, dynamic> toJson() => {

        'id': id,

        'title': title,

        'completed': completed,

      };

}


class ApiService {

  // JSONPlaceholder base URL

  static const String baseUrl = 'https://jsonplaceholder.typicode.com';


  // GET: ambil daftar todos (contoh resource)

  static Future<List<TodoItem>> fetchTodos({int limit = 20}) async {

    final url = Uri.parse('$baseUrl/todos?_limit=$limit');

    final res = await http.get(url);

    if (res.statusCode == 200) {

      final List list = jsonDecode(res.body);

      return list.map((e) => TodoItem.fromJson(e)).toList();

    } else {

      throw Exception('Failed to load todos: ${res.statusCode}');

    }

  }


  // POST: buat todo baru

  static Future<TodoItem> createTodo(String title) async {

    final url = Uri.parse('$baseUrl/todos');

    final res = await http.post(url,

        headers: {'Content-Type': 'application/json; charset=UTF-8'},

        body: jsonEncode({'title': title, 'completed': false, 'userId': 1}));

    if (res.statusCode == 201) {

      return TodoItem.fromJson(jsonDecode(res.body));

    } else {

      throw Exception('Failed to create todo: ${res.statusCode}');

    }

  }


  // PUT: update todo

  static Future<TodoItem> updateTodo(TodoItem todo) async {

    final url = Uri.parse('$baseUrl/todos/${todo.id}');

    final res = await http.put(url,

        headers: {'Content-Type': 'application/json; charset=UTF-8'},

        body: jsonEncode(todo.toJson()));

    if (res.statusCode == 200) {

      return TodoItem.fromJson(jsonDecode(res.body));

    } else {

      throw Exception('Failed to update todo: ${res.statusCode}');

    }

  }


  // DELETE: hapus todo

  static Future<bool> deleteTodo(int id) async {

    final url = Uri.parse('$baseUrl/todos/$id');

    final res = await http.delete(url);

    // JSONPlaceholder returns 200 with empty body

    return res.statusCode == 200 || res.statusCode == 204;

  }

}


class HomePage extends StatefulWidget {

  const HomePage({super.key});


  @override

  State<HomePage> createState() => _HomePageState();

}


class _HomePageState extends State<HomePage> {

  late Future<List<TodoItem>> _futureTodos;

  final TextEditingController _newTodoController = TextEditingController();

  List<TodoItem> _todos = [];

  bool _loadingAction = false;


  @override

  void initState() {

    super.initState();

    _futureTodos = ApiService.fetchTodos(limit: 30);

    _futureTodos.then((value) => setState(() => _todos = value)).catchError((e) {

      if (kDebugMode) print(e);

    });

  }


  Future<void> _refresh() async {

    setState(() => _futureTodos = ApiService.fetchTodos(limit: 30));

    final newList = await _futureTodos;

    setState(() => _todos = newList);

  }


  Future<void> _createTodo() async {

    final title = _newTodoController.text.trim();

    if (title.isEmpty) return;

    setState(() => _loadingAction = true);

    try {

      final created = await ApiService.createTodo(title);

      // JSONPlaceholder returns id = 201 etc; append to local list for UX

      setState(() {

        _todos.insert(0, created);

        _newTodoController.clear();

      });

      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Berhasil membuat todo (POST)')));

    } catch (e) {

      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error create: $e')));

    } finally {

      setState(() => _loadingAction = false);

    }

  }


  Future<void> _toggleCompleted(TodoItem todo) async {

    final updated = TodoItem(id: todo.id, title: todo.title, completed: !todo.completed);

    setState(() => _loadingAction = true);

    try {

      final res = await ApiService.updateTodo(updated);

      setState(() {

        final idx = _todos.indexWhere((t) => t.id == todo.id);

        if (idx != -1) _todos[idx] = res;

      });

      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Berhasil update (PUT)')));

    } catch (e) {

      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error update: $e')));

    } finally {

      setState(() => _loadingAction = false);

    }

  }


  Future<void> _editTodoDialog(TodoItem todo) async {

    final controller = TextEditingController(text: todo.title);

    final result = await showDialog<bool>(

      context: context,

      builder: (context) => AlertDialog(

        title: const Text('Edit Todo (PUT)'),

        content: TextField(

          controller: controller,

          decoration: const InputDecoration(labelText: 'Judul'),

        ),

        actions: [

          TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Batal')),

          ElevatedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Simpan')),

        ],

      ),

    );


    if (result == true) {

      final newTitle = controller.text.trim();

      if (newTitle.isEmpty) return;

      setState(() => _loadingAction = true);

      try {

        final updated = TodoItem(id: todo.id, title: newTitle, completed: todo.completed);

        final res = await ApiService.updateTodo(updated);

        setState(() {

          final idx = _todos.indexWhere((t) => t.id == todo.id);

          if (idx != -1) _todos[idx] = res;

        });

        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Berhasil menyimpan perubahan')));

      } catch (e) {

        ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error save: $e')));

      } finally {

        setState(() => _loadingAction = false);

      }

    }

  }


  Future<void> _deleteTodoConfirm(TodoItem todo) async {

    final yes = await showDialog<bool>(

      context: context,

      builder: (context) => AlertDialog(

        title: const Text('Hapus Todo (DELETE)?'),

        content: Text('Yakin ingin menghapus: "${todo.title}" ?'),

        actions: [

          TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Batal')),

          ElevatedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Hapus')),

        ],

      ),

    );


    if (yes == true) {

      setState(() => _loadingAction = true);

      try {

        final ok = await ApiService.deleteTodo(todo.id);

        if (ok) {

          setState(() => _todos.removeWhere((t) => t.id == todo.id));

          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Berhasil dihapus')));

        } else {

          throw Exception('Delete failed');

        }

      } catch (e) {

        ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error delete: $e')));

      } finally {

        setState(() => _loadingAction = false);

      }

    }

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: const Text('REST API Demo — ToDo Manager'),

        actions: [

          IconButton(onPressed: _refresh, icon: const Icon(Icons.refresh), tooltip: 'GET (refresh)'),

        ],

      ),

      body: Column(

        children: [

          Padding(

            padding: const EdgeInsets.all(12.0),

            child: Row(

              children: [

                Expanded(

                  child: TextField(

                    controller: _newTodoController,

                    decoration: const InputDecoration(labelText: 'Tambah todo baru (POST)', border: OutlineInputBorder()),

                    onSubmitted: (_) => _createTodo(),

                  ),

                ),

                const SizedBox(width: 8),

                _loadingAction

                    ? const SizedBox(width: 48, height: 48, child: Center(child: CircularProgressIndicator()))

                    : ElevatedButton(onPressed: _createTodo, child: const Text('Tambah')),

              ],

            ),

          ),

          Expanded(

            child: _todos.isEmpty

                ? FutureBuilder<List<TodoItem>>(

                    future: _futureTodos,

                    builder: (context, snapshot) {

                      if (snapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator());

                      if (snapshot.hasError) return Center(child: Text('Error: ${snapshot.error}'));

                      final list = snapshot.data ?? [];

                      if (list.isEmpty) return const Center(child: Text('Tidak ada data'));

                      return _buildList(list);

                    },

                  )

                : _buildList(_todos),

          ),

        ],

      ),

      floatingActionButton: FloatingActionButton(

        onPressed: () async {

          // Show short info dialog about HTTP methods

          await showDialog<void>(

            context: context,

            builder: (context) => AlertDialog(

              title: const Text('Metode HTTP ringkas'),

              content: const Text('GET = membaca data\nPOST = membuat data baru\nPUT = memperbarui data (seluruh resource)\nDELETE = menghapus data'),

              actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Tutup'))],

            ),

          );

        },

        child: const Icon(Icons.info_outline),

      ),

    );

  }


  Widget _buildList(List<TodoItem> list) {

    return ListView.separated(

      padding: const EdgeInsets.all(12),

      itemCount: list.length,

      separatorBuilder: (_, __) => const SizedBox(height: 8),

      itemBuilder: (context, index) {

        final todo = list[index];

        return Card(

          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),

          elevation: 3,

          child: ListTile(

            leading: Checkbox(value: todo.completed, onChanged: (_) => _toggleCompleted(todo)),

            title: Text(todo.title, maxLines: 2, overflow: TextOverflow.ellipsis),

            subtitle: Text('ID: ${todo.id}'),

            trailing: Row(

              mainAxisSize: MainAxisSize.min,

              children: [

                IconButton(onPressed: () => _editTodoDialog(todo), icon: const Icon(Icons.edit)),

                IconButton(onPressed: () => _deleteTodoConfirm(todo), icon: const Icon(Icons.delete)),

              ],

            ),

          ),

        );

      },

    );

  }

}


Share:

🎨 Eksperimen Flutter Web di Zapp.run: Dari Menampilkan Gambar, Teks, Tombol, hingga Bentuk Love ❤️

🎨 Eksperimen Flutter Web di Zapp.run: Dari Menampilkan Gambar, Teks, Tombol, hingga Bentuk Love ❤️

1. Pendahuluan

Flutter bukan hanya untuk membuat aplikasi Android dan iOS, tapi juga bisa untuk Web.
Dengan bantuan Zapp.run, kita bisa langsung membuat dan menjalankan aplikasi Flutter tanpa perlu instalasi di laptop.
Di eksperimen kali ini, kita akan membuat:

  • Menampilkan gambar dari assets

  • Menampilkan teks

  • Membuat tombol dengan aksi

  • Memotong gambar menjadi bentuk hati (love)


2. Menyiapkan Proyek di Zapp.run

Langkah-langkah awal:

  1. Buka https://zapp.run

  2. Pilih Create → Flutter Web

  3. Buat folder assets di dalam project

  4. Upload gambar (mantan2.jpg atau gambar lain) ke folder assets

  5. Buka file pubspec.yaml dan tambahkan:

    flutter:
      assets:
        - assets/mantan2.jpg
    
  6. Buka file main.dart dan hapus kode default


3. Kode Full Flutter: Gambar, Teks, Tombol, dan Bentuk Love

Berikut adalah kode lengkap yang bisa langsung dijalankan di Zapp.run:

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gambar Love, Teks, dan Tombol',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.pink),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Flutter Love Shape Demo"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Gambar bentuk hati ❤️
            ClipPath(
              clipper: HeartClipper(),
              child: Image.asset(
                'assets/mantan2.jpg',
                width: 200,
                height: 200,
                fit: BoxFit.cover,
              ),
            ),
            const SizedBox(height: 20),

            // Menampilkan teks
            const Text(
              "Halo dari Flutter Web di Zapp.run!",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),

            // Menampilkan tombol
            ElevatedButton(
              onPressed: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text("Tombol ditekan!")),
                );
              },
              child: const Text("Klik Saya"),
            ),
          ],
        ),
      ),
    );
  }
}

// CustomClipper untuk bentuk hati ❤️
class HeartClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double width = size.width;
    double height = size.height;

    Path path = Path();
    path.moveTo(width / 2, height * 0.75);
    path.cubicTo(
      width * 1.2, height * 0.45,
      width * 0.8, height * -0.4,
      width / 2, height * 0.3,
    );
    path.cubicTo(
      width * 0.2, height * -0.4,
      -width * 0.2, height * 0.45,
      width / 2, height * 0.75,
    );
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

4. Penjelasan Kode

  • Image.asset() → untuk menampilkan gambar dari folder assets

  • ClipPath + CustomClipper → memotong gambar menjadi bentuk hati ❤️

  • Text() → menampilkan teks dengan style tebal dan ukuran tertentu

  • ElevatedButton → membuat tombol dengan aksi ketika ditekan

  • SnackBar → pesan pop-up sementara saat tombol ditekan


5. Hasil Akhir

Saat dijalankan, aplikasi akan menampilkan:

  1. Gambar berbentuk hati

  2. Teks “Halo dari Flutter Web di Zapp.run!”

  3. Tombol “Klik Saya” yang memunculkan pesan di bawah layar

          

jika ingin mencoba aplikasi silahkan klik ling berikut:


6. Kesimpulan

Dengan Flutter Web di Zapp.run, kita bisa bereksperimen cepat membuat UI cantik tanpa ribet instalasi.
Teknik ini bisa dikembangkan untuk membuat galeri foto, kartu ucapan, atau aplikasi kreatif lainnya.
Bahkan, kita bisa memadukan bentuk unik lain seperti lingkaran, bintang, atau bentuk custom sesuai imajinasi.


Salam, Redaksi.

Share:

Pembelajaran konsep CRUD SQLite di Flutter Web (seperti Zapp.run)

Untuk dijalankan di web Flutter seperti Zapp.run, SQLite tidak bisa langsung digunakan, karena SQLite adalah database native yang hanya berjalan di Android, iOS, atau desktop, bukan di web browser.

Namun, untuk pembelajaran konsep CRUD di Flutter Web (seperti Zapp.run), kamu bisa mensimulasikan database SQLite dengan List lokal (in-memory) agar siswa tetap bisa belajar proses Create, Read, Update, Delete.


✅ Solusi: CRUD Simulasi Tanpa SQLite (untuk Web Flutter seperti Zapp.run)

Berikut adalah contoh kode Flutter CRUD tanpa SQLite, tapi berperilaku seperti SQLite, cocok untuk pembelajaran di Zapp.run:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CRUD Flutter Web',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const CrudPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class CrudPage extends StatefulWidget {
  const CrudPage({super.key});

  @override
  State<CrudPage> createState() => _CrudPageState();
}

class _CrudPageState extends State<CrudPage> {
  final TextEditingController _controller = TextEditingController();
  List<String> _items = [];
  int? _editingIndex;

  void _createItem(String value) {
    setState(() {
      _items.add(value);
      _controller.clear();
    });
  }

  void _updateItem(String value) {
    setState(() {
      if (_editingIndex != null) {
        _items[_editingIndex!] = value;
        _editingIndex = null;
        _controller.clear();
      }
    });
  }

  void _deleteItem(int index) {
    setState(() {
      _items.removeAt(index);
    });
  }

  void _startEdit(int index) {
    setState(() {
      _controller.text = _items[index];
      _editingIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CRUD Sederhana (Simulasi SQLite)'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: _controller,
              decoration: const InputDecoration(labelText: 'Masukkan data'),
            ),
            const SizedBox(height: 10),
            Row(
              children: [
                ElevatedButton(
                  onPressed: () {
                    if (_editingIndex == null) {
                      _createItem(_controller.text);
                    } else {
                      _updateItem(_controller.text);
                    }
                  },
                  child: Text(_editingIndex == null ? 'Tambah' : 'Update'),
                ),
                const SizedBox(width: 10),
                if (_editingIndex != null)
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _editingIndex = null;
                        _controller.clear();
                      });
                    },
                    style: ElevatedButton.styleFrom(backgroundColor: Colors.grey),
                    child: const Text('Batal'),
                  )
              ],
            ),
            const SizedBox(height: 20),
            Expanded(
              child: ListView.builder(
                itemCount: _items.length,
                itemBuilder: (_, index) => Card(
                  child: ListTile(
                    title: Text(_items[index]),
                    trailing: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        IconButton(
                          icon: const Icon(Icons.edit, color: Colors.orange),
                          onPressed: () => _startEdit(index),
                        ),
                        IconButton(
                          icon: const Icon(Icons.delete, color: Colors.red),
                          onPressed: () => _deleteItem(index),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

💡 Penjelasan Singkat:

Fungsi Penjelasan
Create Menambahkan data ke list _items
Read Menampilkan data dalam ListView
Update Mengubah data yang sudah ada
Delete Menghapus data dari list

📚 Pembelajaran Lanjutan

Untuk materi pembelajaran lebih lanjut:

  • Di Android Studio ➜ bisa gunakan SQLite asli dengan package sqflite.

  • Di Flutter Web ➜ bisa gunakan Firestore (Firebase) atau localStorage (simulasi).

InsyaAlloh postingan selanjutnya nanti akan coba versi SQLite asli untuk Android/iOS, tunggu ya.


Salam : Redaksi 

Share:

🚀 Projek Flutter Keren: Tampilkan Lokasi SMKN 1 Banjar dengan Google Maps Web!

🚀 Projek Flutter Keren: Tampilkan Lokasi SMKN 1 Banjar dengan Google Maps Web!

Halo, teman-teman pejuang RPL! 🌟

Kalian pernah nggak sih, bingung bikin aplikasi Flutter yang bisa nampilin Google Maps langsung di dalam tampilan web? Atau, kalian pengin bikin aplikasi keren yang bisa kasih rute ke sekolah dan kontak langsung via WhatsApp buat calon siswa atau orang tua?

Tenang, kita bakal belajar bareng dari projek nyata yang menampilkan lokasi SMKN 1 Banjar pakai Flutter Web + Google Maps! 😍 Cocok banget buat kalian kelas 12 RPL yang mau naik level 🔥 dan juga buat pembaca umum yang tertarik di dunia coding Flutter.


💡 Apa yang Akan Kita Pelajari?

  • Menampilkan Google Maps Embed di Flutter Web

  • Membuka link rute Google Maps

  • Menambahkan tombol untuk kontak WhatsApp

  • Menggunakan HtmlElementView untuk tampilan berbasis HTML di Flutter Web

  • Memahami konsep platformViewRegistry


🧠 Konsep Dasarnya Dulu Yuk

Di Flutter Web, kalau kita mau nampilin iframe (seperti Google Maps Embed), kita nggak bisa pakai widget standar kayak Container atau WebView. Kita perlu pakai sesuatu yang bisa rendering HTML langsung — yaitu HtmlElementView.

Tapi sebelum itu, kita harus daftarin dulu HTML element-nya dengan ui.platformViewRegistry.registerViewFactory.


🔧 Full Source Code-nya (Flutter Web Only)


import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'dart:html' as html;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SMKN 1 Banjar',
      theme: ThemeData(
        primarySwatch: Colors.indigo,
        scaffoldBackgroundColor: Colors.grey[50],
        textTheme: const TextTheme(
          bodyMedium: TextStyle(fontSize: 16),
        ),
      ),
      home: const HomeScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    const String viewID = 'google-maps-iframe';
    const String mapEmbedUrl =
        'https://maps.google.com/maps?q=-7.370330346793185,108.52705617634716&z=15&output=embed';

    // Daftarkan iframe Google Maps
    ui.platformViewRegistry.registerViewFactory(viewID, (int viewId) {
      return html.IFrameElement()
        ..src = mapEmbedUrl
        ..style.border = 'none';
    });

    return Scaffold(
      appBar: AppBar(
        title: const Text('SMKN 1 Banjar'),
        actions: [
          TextButton(
            onPressed: () {},
            child: const Text('Beranda', style: TextStyle(color: Colors.white)),
          ),
          TextButton(
            onPressed: () {},
            child: const Text('Profil', style: TextStyle(color: Colors.white)),
          ),
          TextButton(
            onPressed: () {},
            child: const Text('Jurusan', style: TextStyle(color: Colors.white)),
          ),
          TextButton(
            onPressed: () {},
            child: const Text('Kontak', style: TextStyle(color: Colors.white)),
          ),
        ],
      ),
      body: Stack(
        children: [
          Center(
            child: Container(
              constraints: const BoxConstraints(maxWidth: 800),
              padding: const EdgeInsets.all(24.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const Text(
                    'SMK Negeri 1 Banjar',
                    style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  const Text(
                    'Sekolah Menengah Kejuruan unggulan di Kota Banjar dengan berbagai jurusan seperti RPL, Pemasaran, DKV, dan lainnya.',
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 24),

                  // Google Maps Embed
                  const SizedBox(
                    width: 700,
                    height: 400,
                    child: HtmlElementView(viewType: viewID),
                  ),

                  const SizedBox(height: 24),

                  ElevatedButton.icon(
                    onPressed: () {
                      html.window.open(
                        'https://www.google.com/maps/dir/?api=1&destination=-7.370330346793185,108.52705617634716',
                        '_blank',
                      );
                    },
                    icon: const Icon(Icons.directions),
                    label: const Text('Rute ke Sekolah'),
                  ),
                  const SizedBox(height: 12),

                  ElevatedButton.icon(
                    onPressed: () {
                      html.window.open(
                        'https://wa.me/6281234567890?text=Halo%20SMKN%201%20Banjar%2C%20saya%20ingin%20bertanya%20mengenai%20PPDB.',
                        '_blank',
                      );
                    },
                    icon: const Icon(Icons.message), // Ganti dari Icons.whatsapp yang error
                    label: const Text('Hubungi via WhatsApp'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.green,
                      foregroundColor: Colors.white,
                    ),
                  ),
                ],
              ),
            ),
          ),

          // 🔽 Floating WhatsApp Button
          Positioned(
            bottom: 20,
            right: 20,
            child: GestureDetector(
              onTap: () {
                html.window.open(
                  'https://wa.me/6281234567890?text=Halo%20SMKN%201%20Banjar%2C%20saya%20ingin%20bertanya%20mengenai%20PPDB.',
                  '_blank',
                );
              },
              child: Container(
                width: 60,
                height: 60,
                decoration: const BoxDecoration(
                  color: Color(0xFF25D366),
                  shape: BoxShape.circle,
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black26,
                      blurRadius: 5,
                      offset: Offset(2, 2),
                    ),
                  ],
                ),
                child: Center(
                  child: Image.network(
                    'https://cdn-icons-png.flaticon.com/512/733/733585.png',
                    width: 32,
                    height: 32,
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

🌐 Preview Fitur Aplikasi

✅ Menampilkan Google Maps lokasi SMKN 1 Banjar
✅ Menyediakan tombol untuk langsung buka rute ke sekolah
✅ Ada tombol WhatsApp yang siap bantu komunikasi calon siswa
✅ Tampilan rapi dan responsif di web

Jika penasaran ingin coba hasil kodingan ini : https://zc6u06mbc6v0.zapp.page/#/


🎓 Pembelajaran untuk Siswa Kelas 12 RPL

Kodingan ini bukan hanya sekadar menampilkan peta. Ini adalah simbol kemampuan kalian menghubungkan dunia nyata dengan teknologi.

Bayangin kalau kamu nanti membuat aplikasi untuk sekolah lain, hotel, toko, bahkan layanan darurat — konsep seperti ini pasti bakal berguna.

🔥 Tantangan Coding Buat Kamu

  • Ganti lokasi map-nya jadi lokasi rumahmu 🏡

  • Tambahkan tombol untuk buka Instagram sekolah 📸

  • Desain ulang UI dengan tema sekolahmu sendiri 🎨

  • Tambahkan fitur "Ajukan pertanyaan" dengan Google Form 📋


📢 Penutup

Dengan project kecil ini, kamu udah belajar banyak:

  • Cara berkomunikasi dengan HTML di Flutter Web

  • Integrasi Google Maps

  • Interaksi tombol yang membuka link eksternal

Jangan lupa untuk eksplorasi lebih jauh, karena dunia Flutter itu luas banget, dan kalian sebagai siswa RPL punya potensi buat bikin aplikasi bermanfaat bukan hanya untuk tugas, tapi juga untuk masyarakat 💪


🔗 Yuk Coba Langsung!

Kamu bisa deploy aplikasi ini ke web pakai:


==============================

Projek hari ini menampilkan lokasi Google Maps di Flutter Web bukan sekadar latihan teknis, tapi punya makna besar dan manfaat nyata (real-world app), terutama untuk siswa kelas 12 RPL yang sebentar lagi akan masuk dunia industri, kuliah, atau wirausaha digital.

Berikut penjelasan apa yang menarik dan bermanfaat dari projek ini:


🎯 1. Aplikasi Dunia Nyata yang Banyak Digunakan

Aplikasi berbasis lokasi sangat umum dan dibutuhkan:

  • Aplikasi sekolah/kampus ➜ tampilkan lokasi dan rute

  • Website bisnis ➜ lokasi toko, kantor, cabang

  • Aplikasi travel ➜ lokasi wisata, penginapan, resto

  • Aplikasi komunitas ➜ lokasi event, tempat pertemuan

  • Aplikasi kurir/ojek online ➜ fitur arah dan tujuan

Artinya: Kemampuan kalian hari ini = skill yang langsung bisa dipakai di proyek nyata. 🔥


💡 2. Belajar Teknologi Dunia Kerja: Embed dan Web Integration

Kalian sedang belajar cara mengintegrasikan layanan luar (Google Maps) ke dalam aplikasi sendiri — ini kemampuan yang penting banget dalam dunia kerja:

  • Web developer → Integrasi API pihak ketiga

  • Mobile developer → Embed Maps & fitur kontak

  • Software engineer → Komunikasi antar-platform

Ini lebih dari coding — ini integrasi sistem, dan itu yang dicari perusahaan!


📱 3. Karya yang Bisa Ditunjukkan (Portofolio Digital)

Projek ini bisa kamu:

  • Deploy ke Web (via Netlify/Firebase Hosting)

  • Pamerkan ke guru lain/sekolah

  • Tampilkan di CV/LinkedIn/portofolio pribadi

  • Jadikan template untuk aplikasi lain

Projek ini bisa jadi langkah awal personal branding kamu sebagai programmer.


💬 4. Komunikasi Digital Zaman Now (WA Button + Maps)

Kamu bukan cuma bikin aplikasi diam, tapi:

  • Bisa dihubungi langsung lewat WhatsApp

  • Bisa diarahkan langsung ke lokasi via Google Maps

Artinya: Kamu bikin aplikasi yang bisa memudahkan orang lain secara nyata. 💥


🧠 5. Proyek Mini, Tapi Ilmunya Maksimal

Dengan hanya 1 halaman Flutter:

  • Kamu belajar iframeHtmlElementView, dan platformViewRegistry

  • Kamu praktek url launcher via html.window.open

  • Kamu memikirkan user experience (UX) dan aksesibilitas

Jadi bukan hanya "bisa bikin", tapi paham logika dan tujuannya.


✨ Penutup: Ini Lebih dari Sekadar Maps

Hari ini kamu belajar coding yang bisa menyambungkan orang ke tempat, dan tempat ke tujuan.

Kamu bukan cuma programmer, kamu penghubung solusi digital.



Kalau postingan ini bermanfaat, bagikan ke temanmu, dan jangan lupa komen ide fitur yang ingin kamu tambahkan di aplikasi ini ya! 🚀


Klik logo Flutter dibawah ini, untuk mencoba di halaman ini!

Salam : Redaksi

Share:

Blogger Tricks

Blogger Themes