• 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.

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:

Chat Admin

Chat via WhatsApp

Blogger Tricks

Blogger Themes