Tutorial Flutter dengan Laravel Rest API #4: Insert dan Upload di Flutter dengan Rest API

Halo teman-teman kita lanjutkan seri tutorialnya, sebelumnya kita kan sudah membuat tampilan daftar post dan get data post dari rest API, untuk tutorial kali ini kita akan belajar bagaimana caranya untuk insert data dan mengirim file berupa foto melalui Rest API.

Langkah 1 – Membuat Layout Insert

Pada langkah pertama kita membuat layout untuk insert, kita buat file dengan nama add_edit_post_screen.dart pada folder screen, kenapa saya kasih nama add edit, karena nanti kita gunakan juga layout tersebut untuk edit data post. Setelah kalian buat filenya kalian buat class dengan nama AddEdtiPostScreen dengan extend StatelessWidget, ketik kode berikut:

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

Selanjutnya kita buat layoutnya yang dimana layout tersebut ada beberapa widget, dimulai dari Scaffold.

Ketik kode berikut:

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Add New Post'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            const Center(
              child: Text('No Image Selected'),
            ),
            const SizedBox(
              height: 8.0,
            ),
            TextButton(
              onPressed: () {},
              child: const Text('Selected Image'),
            ),
            const SizedBox(
              height: 8.0,
            ),
            TextFormField(
              textInputAction: TextInputAction.next,
              onFieldSubmitted: (value) {},
              decoration: const InputDecoration(
                labelText: 'Title',
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please Insert Title';
                } else {
                  return null;
                }
              },
            ),
            const SizedBox(
              height: 16.0,
            ),
            TextFormField(
              textInputAction: TextInputAction.done,
              keyboardType: TextInputType.multiline,
              maxLines: null,
              decoration: const InputDecoration(
                labelText: 'Content',
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please Insert Some Text';
                } else {
                  return null;
                }
              },
            ),
            const SizedBox(height: 12),
            SizedBox(
              width: double.infinity,
              child: FilledButton(
                onPressed: () {},
                child: const Text('Submit'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Dari kode di atas ada perubahan yaitu

return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Add New Post'),
      ),
      body:,
      .......
      ....
   )

kode di atas yaitu widget Scaffold, widget ini merupakan strutur dasar atau kerangka aplikasi. Di dalam scaffold ini kita isi 2 paramerter:

  1. appbar : appbar ini yaitu tampilan toolbar yang ada di atas layout.
  2. body : body ini digunakan untuk konten utama atau area utama.
Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            const Center(
              child: Text('No Image Selected'),
            ),
            const SizedBox(
              height: 8.0,
            ),
            TextButton(
              onPressed: () {},
              child: const Text('Selected Image'),
            ),
            const SizedBox(
              height: 8.0,
            ),
            TextFormField(
              textInputAction: TextInputAction.next,
              onFieldSubmitted: (value) {},
              decoration: const InputDecoration(
                labelText: 'Title',
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please Insert Title';
                } else {
                  return null;
                }
              },
            ),
            const SizedBox(
              height: 16.0,
            ),
            TextFormField(
              textInputAction: TextInputAction.done,
              keyboardType: TextInputType.multiline,
              maxLines: null,
              decoration: const InputDecoration(
                labelText: 'Content',
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please Insert Some Text';
                } else {
                  return null;
                }
              },
            ),
            const SizedBox(height: 12),
            SizedBox(
              width: double.infinity,
              child: FilledButton(
                onPressed: () {},
                child: const Text('Submit'),
              ),
            ),
          ],
        ),
      ),

Pada kode diatas kita membuat widget Padding() dengan nilai padding semua sisi jaraknya 16, di dalam Widget padding ini kita buat tampilan untuk pilih gambar lalu, 2 widget TextFormField() dan 1 tombol untuk action mengirim data ke rest API.

Sebelum kalian cek hasilnya kita beri action navigasi pada FloatingActionButton yang ada pada layout Home, tambahkan kode berikut pada tanda yang kita beri komentar //TODO action to add pada argument onPressed.

Ketik kode berikut:

Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => const AddEditPostScreen(),
            ),
          );

sehingga menjadi berikut

.......
.........
floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => const AddEditPostScreen(),
            ),
          );
        },
        child: const Icon(Icons.add),
      ),
..............

Setelah sudah kalian run lalu kalian tap pada floatingbutton, tampilan AddEditPostScreen akan menjadi berikut:

Langkah 2 – Mengirim Data ke Rest API

Langkah selanjutnya kita akan mengirim data ke rest api, kita buka layout add_edit_post_screen.dart, karena ada perubahan pada layout ini, kita rubah dulu yang awalnya StatelessWidget kita rubah menjadi StatefulWidget dengan megarahkan cursor pada tulisan statelessWidget lalu gunakan shortcut untuk windows atau linux ctrl + ., sedangankan windows command + . .

setelah itu ketik kode berikut di dalam class _AddEditPostScreenState

  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _contentController = TextEditingController();
  final _titleFocus = FocusNode();
  final _contentFocus = FocusNode();
  File? _image;
  final picker = ImagePicker();
  final Repository apiService = Repository();

  Future getImage() async {
    final pickedFile = await picker.pickImage(source: ImageSource.gallery);
    setState(() {
      if (pickedFile != null) {
        _image = File(pickedFile.path);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('No Image Selected')),
        );
      }
    });
  }

  @override
  void dispose() {
    _titleController.dispose();
    _contentController.dispose();
    super.dispose();
  }

  Future<void> _submitData() async {
    if (_formKey.currentState!.validate() && _image != null) {
      bool success = await apiService.insertPost(
        _image,
        _titleController.text,
        _contentController.text,
      );

      if (!mounted) return;
      if (success) {
        Navigator.pop(context, true);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Failed to Create Post')),
        );
      }
    }
  }

sehingga keseluruhan kode menjadi berikut:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_pemula/api/repository.dart';
import 'package:image_picker/image_picker.dart';

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

  @override
  State<AddEditPostScreen> createState() => _AddEditPostScreenState();
}

class _AddEditPostScreenState extends State<AddEditPostScreen> {
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _contentController = TextEditingController();
  final _titleFocus = FocusNode();
  final _contentFocus = FocusNode();
  File? _image;
  final picker = ImagePicker();
  final Repository apiService = Repository();

  Future getImage() async {
    final pickedFile = await picker.pickImage(source: ImageSource.gallery);
    setState(() {
      if (pickedFile != null) {
        _image = File(pickedFile.path);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('No Image Selected')),
        );
      }
    });
  }

  @override
  void dispose() {
    _titleController.dispose();
    _contentController.dispose();
    _titleFocus.dispose();
    _contentFocus.dispose();
    super.dispose();
  }

  Future<void> _submitData() async {
    if (_formKey.currentState!.validate() && _image != null) {
      bool success = await apiService.insertPost(
        _image,
        _titleController.text,
        _contentController.text,
      );

      if (!mounted) return;
      if (success) {
        Navigator.pop(context, true);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Failed to Create Post')),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Add New Post'),
      ),
      body: Form(
        key: _formKey,
        child: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: [
                Center(
                  child: _image == null
                      ? const Text('No Image Selected')
                      : Image.file(
                          _image!,
                          height: 200,
                          width: 200,
                        ),
                ),
                const SizedBox(
                  height: 8.0,
                ),
                TextButton(
                  onPressed: getImage,
                  child: const Text('Selected Image'),
                ),
                const SizedBox(
                  height: 8.0,
                ),
                TextFormField(
                  controller: _titleController,
                  focusNode: _titleFocus,
                  textInputAction: TextInputAction.next,
                  onFieldSubmitted: (value) {},
                  decoration: const InputDecoration(
                    labelText: 'Title',
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please Insert Title';
                    } else {
                      return null;
                    }
                  },
                ),
                const SizedBox(
                  height: 16.0,
                ),
                TextFormField(
                  controller: _contentController,
                  focusNode: _contentFocus,
                  textInputAction: TextInputAction.done,
                  keyboardType: TextInputType.multiline,
                  maxLines: null,
                  decoration: const InputDecoration(
                    labelText: 'Content',
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please Insert Some Text';
                    } else {
                      return null;
                    }
                  },
                ),
                const SizedBox(height: 12),
                SizedBox(
                  width: double.infinity,
                  child: FilledButton(
                    onPressed: () => _submitData(),
                    child: const Text('Submit'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Perubahan pada kode diatas:

  • final _formKey = GlobalKey<FormState>(); = Membuat kunci global untuk mengidentifikasi f.
  • final _titleController = TextEditingController(); = Membuat pengontrol teks untuk title.
  • final _contentController = TextEditingController(); = Membuat pengontrol teks untuk konten.
  • final _titleFocus = FocusNode(); = Membuat node fokus untuk judul.
  • final _contentFocus = FocusNode(); = Membuat node fokus untuk konten.
  • File? _image; = Deklarasi variabel untuk menyimpan file gambar yang diambil melalu library ImagePicker.
  • final picker = ImagePicker(); = Membuat inisialiasi library imagePicker yang sudah di install.
  • final Repository apiService = Repository(); = inisialiasai kedalam object untuk akses ke Rest API

Untuk fungsi getImage() ini digunakan untuk mengambil gambar dari device kita, menggunakan async lalu di dalam fungsi tersebut kita menggunakan library image_picker. lalu kita panggil pada widget TextButton di argument OnPressed.

Untuk fungsi dispose() ini digunakan untuk menghapus controller dan focusnode ketika widget di hancurkan.

Fungsi ini dipanggil agar tidak terjadi memmory leak atau kebocoran memori.

Untuk fungsi submitData() digunakan untuk mengirim data, yang di dalam fungsi ini terdapat pengecekan terlebih dahulu _formKey.currentState!.validate() && _image != null ketika form sudah ke isi semua dan image tidak bernilai null atau kosong maka akan mengirim data melalu API, yang dimana kita masukkan pada variable success karena nilai kembalian dari hasil variabel apiService.insertPost adalah true atau false. Ketika succes maka akan kembali ke halaman utama jika gagal akan menampilkan pesan Failed to Create Post.

Lalu fungsi submitData() kita paggil pada widget FilledButton pada argument onPressed. Sekarang kalian jalankan lalu coba kita masukkan gambar, title, dan content, jika berhasil maka akan kembali ke halaman home, tapi coba lihat data tidak bertambah pada halaman home. Agar data dapat ter update maka rubah kode pada FloatingActionButton pada onPressed menjadi berikut:

..........
floatingActionButton: FloatingActionButton(
        onPressed: () async {
          var result = await Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => const AddEditPostScreen(),
            ),
          );
          if (result == true) {
            _currentPage = 1;
            _posts.clear();
            _hasMore = true;
            _loadPosts();
          }
        },

...............

Sekarang coba run dan masukkan data lagi, kalau berhasi data akan terupdate.

Kesimpulan

Ok teman pada artikel kita kali ini kita sudah belajar cara input data post dan cara memvalidasi data ketika akan mengirim ke server Rest Api, utuk selanjutnya kita akan belajar bagaimana cara edit dan update data.

Tutorial Flutter dengan Laravel Rest API #5: Edit dan Update di Flutter dengan Rest API


Membangun Website dan Aplikasi Android Desa Dengan Laravel, React.js dan React Native

Membangun Aplikasi dan Website News Dengan Laravel, React.js dan Android

Faisal Mahadi
Faisal Mahadi

Mobile Apps Developer | Android Enthusiast | Keep Learning | Android Dev
Serta Owner Hariankoding.com

Articles: 8