Debug School

rakesh kumar
rakesh kumar

Posted on

What is the role of view model in flutter

In Flutter, the ViewModel is typically used in conjunction with the MVVM (Model-View-ViewModel) architecture pattern. It acts as a bridge between the UI (View) and the data layer (Model). Here are some common tasks you can do in a ViewModel in Flutter, along with examples:

  1. State Management The ViewModel manages the state of the UI components. This includes fetching data, managing loading states, and updating the UI.

Example:

class CounterViewModel extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Data Fetching The ViewModel can handle data fetching from APIs or databases.

Example:

class UserViewModel extends ChangeNotifier {
  final ApiService apiService;
  List<User> users = [];
  bool isLoading = false;

  UserViewModel(this.apiService);

  Future<void> fetchUsers() async {
    isLoading = true;
    notifyListeners();

    try {
      users = await apiService.getUsers();
    } catch (e) {
      print("Error fetching users: $e");
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Data Transformation The ViewModel can transform raw data into a format that is easier to use in the UI.

Example:

class ProductViewModel extends ChangeNotifier {
  List<Product> products = [];

  List<Product> get discountedProducts {
    return products.where((product) => product.isDiscounted).toList();
  }

  void setProducts(List<Product> newProducts) {
    products = newProducts;
    notifyListeners();
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Error Handling The ViewModel can manage error states and provide error messages to the UI.

Example:

class LoginViewModel extends ChangeNotifier {
  final AuthService authService;
  bool isLoading = false;
  String? errorMessage;

  LoginViewModel(this.authService);

  Future<void> login(String email, String password) async {
    isLoading = true;
    errorMessage = null;
    notifyListeners();

    try {
      await authService.login(email, password);
    } catch (e) {
      errorMessage = "Login failed: $e";
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Validation The ViewModel can perform validation logic for user inputs.

Example:

class FormViewModel extends ChangeNotifier {
  String _email = '';
  String _password = '';
  String? _emailError;
  String? _passwordError;

  String? get emailError => _emailError;
  String? get passwordError => _passwordError;

  void setEmail(String email) {
    _email = email;
    _emailError = _validateEmail(email);
    notifyListeners();
  }

  void setPassword(String password) {
    _password = password;
    _passwordError = _validatePassword(password);
    notifyListeners();
  }

  String? _validateEmail(String email) {
    if (email.isEmpty) {
      return 'Email cannot be empty';
    }
    if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(email)) {
      return 'Enter a valid email';
    }
    return null;
  }

  String? _validatePassword(String password) {
    if (password.isEmpty) {
      return 'Password cannot be empty';
    }
    if (password.length < 6) {
      return 'Password must be at least 6 characters';
    }
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Dependency Injection The ViewModel can use dependency injection to get instances of services or repositories.

Example:

class HomeViewModel extends ChangeNotifier {
  final UserService userService;
  final ProductService productService;

  HomeViewModel(this.userService, this.productService);

  Future<void> initialize() async {
    await fetchUsers();
    await fetchProducts();
  }

  Future<void> fetchUsers() async {
    // Fetch users logic
  }

  Future<void> fetchProducts() async {
    // Fetch products logic
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Navigation The ViewModel can handle navigation logic, making it easier to test and manage.

Example:

class NavigationViewModel extends ChangeNotifier {
  final NavigationService navigationService;

  NavigationViewModel(this.navigationService);

  void navigateToDetailPage(int itemId) {
    navigationService.navigateTo('detail/$itemId');
  }
}
Enter fullscreen mode Exit fullscreen mode

Using ViewModel in the Widget Tree
Make sure you provide the ViewModel in the widget tree and use it in your widgets.

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CounterViewModel()),
        ChangeNotifierProvider(create: (_) => UserViewModel(ApiService())),
        // Add other ViewModels here
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterViewModel = Provider.of<CounterViewModel>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Counter: ${counterViewModel.counter}'),
            ElevatedButton(
              onPressed: counterViewModel.increment,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Filtering On feteched Api Data

Filtering Fetched Data in ViewModel
Suppose you have an API that fetches a list of products, and you want to filter the products based on their category. Here’s how you can do it:

Define the Product Model:

class Product {
  final int id;
  final String name;
  final String category;

  Product({
    required this.id,
    required this.name,
    required this.category,
  });

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'],
      name: json['name'],
      category: json['category'],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Create the Product API Service:

class ApiService {
  Future<List<Product>> fetchProducts() async {
    final response = await http.get(Uri.parse('https://api.example.com/products'));
    if (response.statusCode == 200) {
      List<dynamic> data = json.decode(response.body);
      return data.map((item) => Product.fromJson(item)).toList();
    } else {
      throw Exception('Failed to load products');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Implement the ViewModel:

class ProductViewModel extends ChangeNotifier {
  final ApiService apiService;
  List<Product> _products = [];
  List<Product> _filteredProducts = [];
  bool isLoading = false;

  ProductViewModel({required this.apiService});

  List<Product> get products => _filteredProducts;

  Future<void> fetchProducts() async {
    isLoading = true;
    notifyListeners();

    try {
      _products = await apiService.fetchProducts();
      _filteredProducts = _products;
    } catch (e) {
      print('Error fetching products: $e');
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }

  void filterProducts(String category) {
    if (category == 'All') {
      _filteredProducts = _products;
    } else {
      _filteredProducts = _products.where((product) => product.category == category).toList();
    }
    notifyListeners();
  }
}
Enter fullscreen mode Exit fullscreen mode

Use the ViewModel in the Widget Tree:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => ProductViewModel(apiService: ApiService())),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ProductScreen(),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Build the Product Screen:

class ProductScreen extends StatefulWidget {
  @override
  _ProductScreenState createState() => _ProductScreenState();
}

class _ProductScreenState extends State<ProductScreen> {
  String _selectedCategory = 'All';

  @override
  void initState() {
    super.initState();
    Future.microtask(() => Provider.of<ProductViewModel>(context, listen: false).fetchProducts());
  }

  @override
  Widget build(BuildContext context) {
    final productViewModel = Provider.of<ProductViewModel>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Products'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: DropdownButton<String>(
              isExpanded: true,
              value: _selectedCategory,
              onChanged: (newValue) {
                setState(() {
                  _selectedCategory = newValue!;
                  productViewModel.filterProducts(_selectedCategory);
                });
              },
              items: ['All', 'Category1', 'Category2', 'Category3'].map((category) {
                return DropdownMenuItem<String>(
                  value: category,
                  child: Text(category),
                );
              }).toList(),
            ),
          ),
          Expanded(
            child: productViewModel.isLoading
                ? Center(child: CircularProgressIndicator())
                : ListView.builder(
                    itemCount: productViewModel.products.length,
                    itemBuilder: (context, index) {
                      final product = productViewModel.products[index];
                      return ListTile(
                        title: Text(product.name),
                        subtitle: Text(product.category),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)