To develop an app that can Fetch news and Display them on your mobile screen using Flutter, you need to follow these steps carefully:
Project Directory Structure
Before diving into the code, let's take a look at the directory structure of our project:

Steps to Create News App in Flutter
Step 1: Create a New Flutter Project
Open your terminal and create a new Flutter project by running the following command:
flutter create news_appNavigate to the project directory:
cd news_appStep 2 : Add packages to your pubspec.yaml file
The http package will help us to connect with api :
dependencies:
http:
Step 3 : Create an Article model :
Head over to model folder and create a file , article.dart , this model will contain details of our article , like author , title,imageUrl etc.
class Article {
final Source source;
final String author;
final String title;
final String description;
final String url;
final String urlToImage;
final String publishedAt;
Article({
required this.source,
required this.author,
required this.title,
required this.description,
required this.url,
required this.urlToImage,
required this.publishedAt,
});
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
source: Source.fromJson(json['source']),
author: json['author'] ?? 'Unknown Author',
title: json['title'],
description: json['description'],
url: json['url'],
urlToImage: json['urlToImage'] ?? '',
publishedAt: json['publishedAt'],
);
}
}
class Source {
final String id;
final String name;
Source({
required this.id,
required this.name,
});
factory Source.fromJson(Map<String, dynamic> json) {
return Source(
id: json['id'] ?? 'unknown',
name: json['name'] ?? 'unknown',
);
}
}
Step 4 : Create a custom widget NewsCard
This widget will receive an Article object and display the info related to that article.
import 'package:flutter/material.dart';
import 'package:news_aggregator_app/model/article.dart';
class NewsCard extends StatelessWidget {
final Article article;
const NewsCard({super.key, required this.article});
@override
Widget build(BuildContext context) {
final String imageUrl = article.urlToImage;
final String title = article.title;
final String description = article.description;
final String author = article.author;
final String source = article.source.name;
final String publishedAt = article.publishedAt;
return Card(
elevation: 6,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
color: const Color(0xFFF5F5F5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image Section
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
child: imageUrl.isNotEmpty
? Image.network(
imageUrl,
height: 200,
width: double.infinity,
fit: BoxFit.cover,
)
: const SizedBox(
height: 200,
child: Center(
child: Icon(
Icons.image_not_supported,
size: 50,
color: Colors.grey,
),
),
),
),
// Content Section
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Source and Author
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
source,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF00796B), // Teal shade
),
),
Text(
'By $author',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
const SizedBox(height: 8),
// Title
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF37474F), // Dark grey shade
),
),
const SizedBox(height: 8),
// Description
Text(
description,
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
// Date Published
Text(
'Published: $publishedAt',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
],
),
),
// Button to Read More
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ElevatedButton(
onPressed: () {
// Implement navigation to the article's URL
},
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xFF009688), // Teal color for button
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: const Center(
child: Text(
'Read More',
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
);
}
}
Step 5 : Setting up api :
We are using Newsapi for this project , you can create a free account using your email id :

Step 6 : Communicate with api :
In your service Folder create a new file news_service.dart. This file will contain necessary logic for our app to communicate with the external api :
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:news_aggregator_app/model/article.dart';
class NewsApiService {
final String apiKey =
'Your_api_key'; // Replace with your actual API key
Future<List<Article>> fetchNews() async {
final String apiUrl =
'https://newsapi.org//v2/everything?q=Technology&from=2024-09-10&sortBy=popularity&apiKey=$apiKey';
final response = await http.get(Uri.parse(apiUrl));
if (response.statusCode == 200) {
final Map<String, dynamic> data = jsonDecode(response.body);
if (data['status'] == 'ok') {
List articles = data['articles'];
return articles.map((article) => Article.fromJson(article)).toList();
} else {
throw Exception('Failed to load news');
}
} else {
throw Exception('Failed to load news');
}
}
}
Don't forget to replace Your_api_key with your actual api key.
Step 7 : Create News page :
Create a new file news_page.dart inside the pages folder, this file has Future builder that will receive a list of Article objects and from the api and display number of NewsCards on the UI:
import 'package:flutter/material.dart';
import 'package:news_aggregator_app/model/article.dart';
import 'package:news_aggregator_app/model/news_card.dart';
import 'package:news_aggregator_app/services/news_service.dart';
class NewsListPage extends StatefulWidget {
const NewsListPage({super.key});
@override
_NewsListPageState createState() => _NewsListPageState();
}
class _NewsListPageState extends State<NewsListPage> {
late Future<List<Article>> futureArticles;
@override
void initState() {
super.initState();
futureArticles = NewsApiService().fetchNews();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('News'),
backgroundColor: const Color(0xFF00796B),
),
body: FutureBuilder<List<Article>>(
future: futureArticles,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (snapshot.hasData) {
final articles = snapshot.data!;
return ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
final article = articles[index];
return NewsCard(article: article);
},
);
} else {
return const Center(child: Text('No News Found'));
}
},
),
);
}
}
Step 8 : Finally modify the main.dart file :
Call the NewsListPage from the root of the app :
import 'package:flutter/material.dart';
import 'package:news_aggregator_app/pages/news_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: NewsListPage(),
);
}
}
Step 9 : Running the App
Save all the files and ensure that your project is correctly set up.
Run the app in the terminal:
flutter runThis will launch the app and show a list of recipe cards on the screen.