Creating a List of Cards from JSON in Flutter
•15 min read
Creating a list of cards from JSON data is a common requirement in Flutter applications. This comprehensive guide will show you how to efficiently parse JSON data and display it in an attractive card layout.
Basic Implementation
1. Define the Data Model
First, create a model class to represent your data:
class CardItem { final String title; final String description; final String imageUrl; final DateTime date; CardItem({ required this.title, required this.description, required this.imageUrl, required this.date, }); factory CardItem.fromJson(Map<String, dynamic> json) { return CardItem( title: json['title'] ?? '', description: json['description'] ?? '', imageUrl: json['imageUrl'] ?? '', date: DateTime.parse(json['date'] ?? DateTime.now().toIso8601String()), ); } Map<String, dynamic> toJson() { return { 'title': title, 'description': description, 'imageUrl': imageUrl, 'date': date.toIso8601String(), }; } }
2. Create a Custom Card Widget
class CustomCard extends StatelessWidget { final CardItem item; const CustomCard({Key? key, required this.item}) : super(key: key); @override Widget build(BuildContext context) { return Card( margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.vertical(top: Radius.circular(12)), child: Image.network( item.imageUrl, height: 200, width: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 200, color: Colors.grey[300], child: Icon(Icons.error, color: Colors.red), ); }, ), ), Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.title, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8), Text( item.description, style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), SizedBox(height: 8), Text( DateFormat('MMM dd, yyyy').format(item.date), style: TextStyle( fontSize: 14, color: Colors.grey[500], ), ), ], ), ), ], ), ); } }
JSON Data Handling
1. Load JSON Data
class CardListScreen extends StatefulWidget { @override _CardListScreenState createState() => _CardListScreenState(); } class _CardListScreenState extends State<CardListScreen> { List<CardItem> cards = []; bool isLoading = true; String? error; @override void initState() { super.initState(); loadCards(); } Future<void> loadCards() async { try { // Simulating API call final response = await http.get( Uri.parse('https://api.example.com/cards'), ); if (response.statusCode == 200) { final List<dynamic> jsonData = json.decode(response.body); setState(() { cards = jsonData.map((json) => CardItem.fromJson(json)).toList(); isLoading = false; }); } else { throw Exception('Failed to load cards'); } } catch (e) { setState(() { error = e.toString(); isLoading = false; }); } } @override Widget build(BuildContext context) { if (isLoading) { return Center(child: CircularProgressIndicator()); } if (error != null) { return Center(child: Text('Error: $error')); } return ListView.builder( itemCount: cards.length, itemBuilder: (context, index) { return CustomCard(item: cards[index]); }, ); } }
Advanced Implementation
1. Animated Card List
class AnimatedCardList extends StatelessWidget { final List<CardItem> cards; const AnimatedCardList({Key? key, required this.cards}) : super(key: key); @override Widget build(BuildContext context) { return ListView.builder( itemCount: cards.length, itemBuilder: (context, index) { return AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, transform: Matrix4.translationValues( 0, index * 10.0, 0, ), child: CustomCard(item: cards[index]), ); }, ); } }
2. Card with Interaction
class InteractiveCard extends StatelessWidget { final CardItem item; const InteractiveCard({Key? key, required this.item}) : super(key: key); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailScreen(item: item), ), ); }, child: Hero( tag: 'card_${item.title}', child: Card( margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.vertical(top: Radius.circular(12)), child: Image.network( item.imageUrl, height: 200, width: double.infinity, fit: BoxFit.cover, ), ), Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.title, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8), Text( item.description, style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), ], ), ), ], ), ), ), ), ); } }
Best Practices
-
Performance Optimization
- Use const constructors where possible
- Implement pagination for large lists
- Cache network images
- Use lazy loading techniques
-
Error Handling
- Implement proper error states
- Show loading indicators
- Handle network timeouts
- Provide retry mechanisms
-
User Experience
- Add pull-to-refresh functionality
- Implement smooth animations
- Provide visual feedback
- Handle edge cases gracefully
Common Issues and Solutions
Issue 1: Memory Management
// Bad Practice ListView( children: cards.map((card) => CustomCard(item: card)).toList(), ); // Good Practice ListView.builder( itemCount: cards.length, itemBuilder: (context, index) => CustomCard(item: cards[index]), );
Issue 2: Image Loading
// Handle image loading errors Image.network( imageUrl, errorBuilder: (context, error, stackTrace) { return Container( color: Colors.grey[300], child: Icon(Icons.error), ); }, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Center(child: CircularProgressIndicator()); }, );
Conclusion
Creating a list of cards from JSON in Flutter requires:
- Proper data modeling
- Efficient JSON parsing
- Optimized list rendering
- Error handling
- Performance considerations
Remember these key points:
- Use appropriate widgets for list rendering
- Implement proper error handling
- Optimize performance for large lists
- Consider user experience
- Follow Flutter best practices