Loading...

  • 17 Apr, 2026

Building a Flutter CRUD App with GetX for User Management

Building a Flutter CRUD App with GetX for User Management

Flutter is a powerful framework for building beautiful, high-performance cross-platform applications. In this tutorial, we'll walk through creating a simple CRUD (Create, Read, Update, Delete) app using GetX, a lightweight and reactive state management library. The app will manage users by fetching, displaying, and adding new users from/to an API.

Overview of the App

The app performs the following operations:

  1. Fetch Users (Read): Retrieve a list of users from an API and display them in a scrollable list.
  2. Create User: Add a new user through a form and post it to the API.
  3. Side Menu Navigation: A user-friendly side menu provides quick access to key app features.

We'll use JSONPlaceholder as the API for demonstration purposes.


Tech Stack

  • Flutter: Framework for cross-platform app development.
  • GetX: For state management, navigation, and dependency injection.
  • HTTP: For API communication.
  • JSONPlaceholder API: Mock API for testing.

Folder Structure

A clear folder structure ensures maintainability:

lib/
├── controllers/
│   └── user_controller.dart  # Handles business logic and state
├── models/
│   └── user_model.dart       # Represents the user data model
├── services/
│   └── api_service.dart      # Manages API interactions
├── views/
│   ├── home_view.dart        # Displays the list of users
│   ├── post_user_view.dart   # Form for creating a new user
│   └── widgets/
│       └── side_menu.dart    # Side menu for navigation
├── routes/
│   └── app_routes.dart       # Centralized route management
└── main.dart                 # App entry point

Code Connections

1. API Integration (api_service.dart)

The ApiService class handles HTTP requests. It includes methods to fetch and create users.

class ApiService {
  final String apiUrl = 'https://jsonplaceholder.typicode.com/users';

  // Fetch users (GET)
  Future<List<User>> fetchUsers() async {
    final response = await http.get(Uri.parse(apiUrl));
    if (response.statusCode == 200) {
      return (json.decode(response.body) as List)
          .map((data) => User.fromJson(data))
          .toList();
    } else {
      throw Exception('Failed to load users');
    }
  }

  // Create user (POST)
  Future<User> createUser(Map<String, dynamic> userData) async {
    final response = await http.post(
      Uri.parse(apiUrl),
      headers: {'Content-Type': 'application/json'},
      body: json.encode(userData),
    );
    if (response.statusCode == 201) {
      return User.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to create user');
    }
  }
}

How it connects:

  • The fetchUsers method retrieves a list of users from the API.
  • The createUser method sends new user data to the API.
  • These methods are called in the UserController to update the app's state.

2. User State Management (user_controller.dart)

The UserController connects the API with the UI. It uses GetX observables to manage state.

class UserController extends GetxController {
  final ApiService apiService = ApiService();

  var users = <User>[].obs;
  var isLoading = true.obs;

  @override
  void onInit() {
    super.onInit();
    fetchUsers();
  }

  // Fetch users and update state
  void fetchUsers() async {
    try {
      isLoading(true);
      users.value = await apiService.fetchUsers();
    } catch (e) {
      Get.snackbar('Error', 'Failed to load users');
    } finally {
      isLoading(false);
    }
  }

  // Create a new user
  void createUser(Map<String, dynamic> userData) async {
    try {
      isLoading(true);
      final newUser = await apiService.createUser(userData);
      users.add(newUser);
      Get.snackbar('Success', 'User created successfully');
    } catch (e) {
      Get.snackbar('Error', 'Failed to create user');
    } finally {
      isLoading(false);
    }
  }
}

How it connects:

  • The fetchUsers method populates the users observable, updating the UI dynamically.
  • The createUser method adds a new user to the users list upon success.
  • Snackbar notifications provide instant feedback.

3. UI Components

Home Screen (home_view.dart): Displays the list of users and includes a floating action button for adding users.

class HomeView extends StatelessWidget {
  final UserController controller = Get.put(UserController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User Management')),
      drawer: SideMenu(), // Include side menu
      body: Obx(() {
        if (controller.isLoading.value) {
          return Center(child: CircularProgressIndicator());
        }
        return ListView.builder(
          itemCount: controller.users.length,
          itemBuilder: (context, index) {
            final user = controller.users[index];
            return ListTile(
              title: Text(user.name),
              subtitle: Text(user.email),
            );
          },
        );
      }),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Get.toNamed('/post-user'); // Navigate to user creation screen
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Create User Screen (post_user_view.dart): A form for creating new users.

class PostUserView extends StatelessWidget {
  final UserController controller = Get.find();
  final TextEditingController nameController = TextEditingController();
  final TextEditingController emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Create User')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: InputDecoration(labelText: 'Name'),
            ),
            SizedBox(height: 16),
            TextField(
              controller: emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                final userData = {
                  'name': nameController.text,
                  'email': emailController.text,
                };
                controller.createUser(userData);
              },
              child: Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }
}

4. Navigation (app_routes.dart)

Centralized route management simplifies navigation.

class AppRoutes {
  static const home = '/';
  static const postUser = '/post-user';

  static final routes = [
    GetPage(name: home, page: () => HomeView()),
    GetPage(name: postUser, page: () => PostUserView()),
  ];
}

How it connects:

  • Routes are defined once and used consistently across the app.

How It All Connects

  1. UserController:
    • Fetches data from ApiService and updates the users observable.
    • Adds new users to the list when createUser is called.
  2. API Service:
    • Handles all HTTP operations, isolating networking logic.
  3. Views:
    • Dynamically update based on changes to users in the controller.
    • Use GetX's Obx for reactive state updates.
  4. Navigation:
    • Simplifies screen transitions using GetX routes.

Conclusion

This CRUD app demonstrates how to use Flutter and GetX to build a scalable, responsive application. The modular approach separates concerns, making the app easy to maintain and extend.

What features would you add to improve this app? Let me know in the comments! 🚀

John Smith

How puzzling all these changes are! I'm never sure what I'm going to turn into a tidy little room.