flutter
/

GetX Folder Structure: Organize Your Flutter App Like a Pro

Last Sync: Today

On this page

14
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Folder Structure: Organize Your Flutter App Like a Pro

Why a Good Folder Structure Matters

A well-organized folder structure is crucial for any Flutter project, especially when using GetX. It helps with:

  • Maintainability: Easy to find and update code.
  • Scalability: Adding new features becomes straightforward.
  • Team Collaboration: Consistent structure makes it easier for multiple developers to work together.
  • Separation of Concerns: Clear boundaries between UI, business logic, and data layers.

Recommended GetX Folder Structure

Here’s the industry-standard folder layout for GetX projects:

TEXTRead-only
1
lib/
├── main.dart
├── app/
│   ├── routes/
│   │   └── app_pages.dart
│   │   └── app_routes.dart
│   ├── bindings/
│   │   └── app_bindings.dart
│   └── utils/
│       ├── constants.dart
│       ├── themes.dart
│       └── helpers/
├── modules/
│   ├── home/
│   │   ├── bindings/
│   │   │   └── home_binding.dart
│   │   ├── controllers/
│   │   │   └── home_controller.dart
│   │   ├── views/
│   │   │   └── home_view.dart
│   │   └── models/
│   │       └── home_model.dart
│   ├── counter/
│   │   ├── bindings/
│   │   ├── controllers/
│   │   ├── views/
│   │   └── models/
│   └── ...
├── services/
│   ├── api_service.dart
│   ├── storage_service.dart
│   └── ...
└── widgets/
    ├── custom_button.dart
    ├── loading_widget.dart
    └── ...

Explanation of Each Folder

  • app/: Contains global app configurations like routes, bindings, and utilities.
  • app/routes/: Defines all route names and pages using GetX’s routing system.
  • app/bindings/: Global bindings (like initial bindings) that are loaded when the app starts.
  • app/utils/: Constants, themes, helper functions, extensions.
  • modules/: Feature-based organization. Each feature (home, profile, cart) has its own subfolder with controllers, views, bindings, and models.
  • services/: Shared services like API calls, local storage, authentication.
  • widgets/: Reusable UI components used across multiple modules.

Feature-Based Organization

The modules folder is where the magic happens. Each module is a self-contained feature. For example, a counter module:

TEXTRead-only
1
modules/
└── counter/
    ├── bindings/
    │   └── counter_binding.dart
    ├── controllers/
    │   └── counter_controller.dart
    ├── views/
    │   └── counter_view.dart
    └── models/
        └── counter_model.dart

Example: Counter Module Implementation

  1. Counter Model (counter_model.dart)

DARTRead-only
1
class CounterModel {
  int count;
  CounterModel({this.count = 0});
}

  1. Counter Controller (counter_controller.dart)

DARTRead-only
1
import 'package:get/get.dart';
import '../models/counter_model.dart';

class CounterController extends GetxController {
  var model = CounterModel().obs;

  void increment() => model.update((val) => val?.count++);
  void decrement() => model.update((val) => val?.count--);
}

  1. Counter Binding (counter_binding.dart)

DARTRead-only
1
import 'package:get/get.dart';
import '../controllers/counter_controller.dart';

class CounterBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<CounterController>(() => CounterController());
  }
}

  1. Counter View (counter_view.dart)

DARTRead-only
1
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/counter_controller.dart';

class CounterView extends GetView<CounterController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Obx(() => Text(
          'Count: ${controller.model.value.count}',
          style: TextStyle(fontSize: 24),
        )),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: controller.decrement,
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: controller.increment,
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Setting Up Routes

In app/routes/app_routes.dart, define all route names:

DARTRead-only
1
part of 'app_pages.dart';

class AppRoutes {
  static const HOME = '/home';
  static const COUNTER = '/counter';
}

In app/routes/app_pages.dart, map routes to pages with their bindings:

DARTRead-only
1
import 'package:get/get.dart';
import '../../modules/home/bindings/home_binding.dart';
import '../../modules/home/views/home_view.dart';
import '../../modules/counter/bindings/counter_binding.dart';
import '../../modules/counter/views/counter_view.dart';

part 'app_routes.dart';

class AppPages {
  static const INITIAL = AppRoutes.HOME;

  static final routes = [
    GetPage(
      name: AppRoutes.HOME,
      page: () => HomeView(),
      binding: HomeBinding(),
    ),
    GetPage(
      name: AppRoutes.COUNTER,
      page: () => CounterView(),
      binding: CounterBinding(),
    ),
  ];
}

Using the Structure in main.dart

DARTRead-only
1
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'app/routes/app_pages.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX App',
      initialRoute: AppPages.INITIAL,
      getPages: AppPages.routes,
      initialBinding: AppBindings(), // optional global bindings
    );
  }
}

Best Practices

  • Keep controllers small: Move business logic to services if a controller becomes too large.
  • Use lazy loading: Use Get.lazyPut() in bindings to improve startup performance.
  • Avoid large global widgets: Keep reusable widgets in the widgets/ folder, not inside view files.
  • Use GetX’s smart management: Let GetX dispose controllers automatically when not needed.
  • Follow consistent naming: Use _controller, _binding, _view suffixes for clarity.
  • Use part files for routes (optional): Group route names and pages in one file with .part to avoid clutter.

Common Mistakes to Avoid

  • ❌ Placing all controllers in one folder – leads to disorganization. ✅ Keep controllers inside their respective modules.
  • ❌ Mixing UI code with business logic in controllers. ✅ Controllers should only hold state and logic, not build widgets.
  • ❌ Hardcoding routes in views. ✅ Use named routes and constants.
  • ❌ Forgetting to dispose streams/timers in onClose(). ✅ Always clean up resources to avoid memory leaks.
  • ❌ Not using bindings and relying on Get.put() inside views. ✅ Use bindings to decouple dependency injection.

Conclusion

A well-structured GetX app makes development faster, collaboration easier, and maintenance a breeze. The feature-based module structure with clear separation of concerns scales beautifully from small projects to enterprise-level applications. Start organizing your app today and reap the benefits of clean architecture.

Try it yourself

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

// Example simplified version of the structure
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX Structure Demo',
      home: CounterView(),
    );
  }
}

// Counter Controller
class CounterController extends GetxController {
  var count = 0.obs;
  void increment() => count++;
  void decrement() => count--;
}

// Counter View
class CounterView extends StatelessWidget {
  final controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter Demo')),
      body: Center(
        child: Obx(() => Text(
          'Count: ${controller.count}',
          style: TextStyle(fontSize: 32),
        )),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: controller.decrement,
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: controller.increment,
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

Where should you place reusable UI components like custom buttons in a GetX project?

A
Inside the views folder of each module
B
In the widgets/ folder
C
In the controllers/ folder
D
In the main.dart file
Q2
of 3

What is the purpose of bindings in GetX?

A
To define routes
B
To manage dependency injection when navigating
C
To store UI constants
D
To handle API calls
Q3
of 3

Which of the following is the recommended folder structure for a feature module?

A
lib/features/feature_name/
B
lib/modules/feature_name/
C
lib/feature_name/
D
lib/views/feature_name/

Previous

getx installation

Next

getx state management

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.