When you start building a real-world rental listing app, things like filtering, asset management (photos/videos), and clean state handling can quickly get complicated.
In this post, I’ll show a practical example of how to build a simple yet scalable architecture using Flutter and Riverpod — clean, testable, and easy to extend.
What We’ll Build
Display a list of rental properties (title, location, price, thumbnail).
Add basic city-based filtering.
Toggle favorites using local state.
Keep the architecture clean with Riverpod instead of Redux-style complexity.
**
Why Riverpod?
**
Simple and predictable.
Test-friendly and easy to compose.
Context-free access (no need for BuildContext).
Works great for both small and large apps.
Code Example
Model:
// models/property.dart
class Property {
final String id;
final String title;
final String city;
final int price;
final String thumbnail;
Property({required this.id, required this.title, required this.city, required this.price, required this.thumbnail});
}
Providers:
// providers/property_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/property.dart';
// sample data provider
final propertyListProvider = Provider>((ref) {
return [
Property(id: 'p1', title: 'Cozy 2BR near downtown', city: 'Dhaka', price: 1200, thumbnail: 'https://...'),
Property(id: 'p2', title: 'Sunny studio', city: 'Chittagong', price: 800, thumbnail: 'https://...'),
];
});
final cityFilterProvider = StateProvider((ref) => null);
final filteredPropertyProvider = Provider>((ref) {
final all = ref.watch(propertyListProvider);
final city = ref.watch(cityFilterProvider);
if (city == null || city.isEmpty) return all;
return all.where((p) => p.city.toLowerCase().contains(city.toLowerCase())).toList();
});
UI Widget:
// ui/property_list.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/property_provider.dart';
class PropertyListView extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final properties = ref.watch(filteredPropertyProvider);
return ListView.builder(
itemCount: properties.length,
itemBuilder: (ctx, i) {
final p = properties[i];
return ListTile(
leading: Image.network(p.thumbnail, width: 56, height: 56, fit: BoxFit.cover),
title: Text(p.title),
subtitle: Text('${p.city} • \$${p.price}'),
onTap: () {},
);
},
);
}
}
Production Tips
Use FutureProvider for API calls with loading/error states.
Implement pagination with ListView.builder.
Store favorites in local storage (Hive or SharedPreferences).
Optimize image loading with caching or CDN.
Keep accessibility in mind — proper contrast, alt-text, and semantics.
For more practical examples and rental app case studies, check out Rentyard
Top comments (0)