Popular Searches
Popular Course Categories
Popular Courses

Build a Flutter News App Using API

Build a Flutter News App Using API

Flutter Practical Projects


Build a Flutter News App Using API


A Flutter News App is a practical project that demonstrates how to build a real-world application that retrieves news articles from an online API and displays them in a clean, user-friendly interface. This project covers Flutter UI development, HTTP requests, REST APIs, JSON parsing, asynchronous programming, API authentication, search, categories, loading states, error handling, and dynamic lists.


In this project, users can view news articles, search for news, browse articles by category, and open an article to read more details. The application can be connected to any suitable news API that provides JSON-based article data.




1. What is a News App?


A News App is an application that retrieves news articles from one or more online sources and presents them to users. Instead of storing all news articles directly inside the Flutter application, the app communicates with a remote API to retrieve current data.


A typical News App can display:



  • Article title

  • Article description

  • Article image

  • Author name

  • Publication date

  • News source

  • Category

  • Search results

  • Article URL


2. Objectives of This Project


After completing this project, you will understand how to:



  • Create a Flutter News App.

  • Connect Flutter to a REST API.

  • Use the http package.

  • Send HTTP GET requests.

  • Work with API authentication.

  • Parse JSON responses.

  • Create Dart model classes.

  • Display dynamic data using ListView.builder.

  • Use Future and async/await.

  • Handle loading states.

  • Handle API errors.

  • Search for news articles.

  • Filter news by category.

  • Create reusable article widgets.

  • Open an article URL.

  • Structure a larger Flutter application.


3. Technologies Used













TechnologyPurpose
FlutterBuilds the application UI
DartProgramming language used by Flutter
Material DesignProvides ready-made UI components
HTTPCommunicates with the news API
REST APIProvides news data
JSONRepresents API response data
FutureRepresents asynchronous operations
async/awaitHandles asynchronous API calls
ListView.builderDisplays a dynamic list of articles

4. News App Architecture


User
  ↓
Flutter News App
  ↓
News Service
  ↓
HTTP Request
  ↓
News API
  ↓
JSON Response
  ↓
Dart Model
  ↓
Flutter UI
  ↓
News Articles

5. Basic Application Flow


Open App
   ↓
Load News
   ↓
Send API Request
   ↓
Receive JSON
   ↓
Parse JSON
   ↓
Create Article Objects
   ↓
Display Article List
   ↓
User Searches or Selects Category
   ↓
Send New API Request
   ↓
Display Updated Results

6. Create a Flutter Project


Create a new Flutter project using the Flutter CLI.


flutter create news_app

Move into the project directory:


cd news_app

Run the application:


flutter run

7. Add the HTTP Package


The Flutter http package provides a simple way to make HTTP requests across supported platforms.


flutter pub add http

Import the package:


import 'package:http/http.dart' as http;

Flutter's official documentation recommends the http package as a straightforward way to make HTTP requests. :contentReference[oaicite:0]{index=0}


8. Android Internet Permission


If the application is deployed to Android and needs internet access, declare the Internet permission in the Android manifest.



This permission is required for Android applications that fetch data from the internet. :contentReference[oaicite:1]{index=1}


9. Suggested Project Structure


news_app/
├── android/
├── ios/
├── lib/
│   ├── main.dart
│   ├── models/
│   │   └── article.dart
│   ├── services/
│   │   └── news_service.dart
│   ├── screens/
│   │   ├── home_page.dart
│   │   └── article_page.dart
│   └── widgets/
│       ├── article_card.dart
│       └── category_chip.dart
├── test/
├── web/
├── pubspec.yaml
└── README.md

For a beginner project, you can initially keep the implementation in main.dart. As the application becomes larger, separating models, services, screens, and widgets improves maintainability.


10. Create the Main Function


void main() {
  runApp(const NewsApp());
}

The main() function is the entry point of the Dart application. The runApp() function places the root widget into the Flutter widget tree.


11. Create the Root Application


class NewsApp extends StatelessWidget {
  const NewsApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'News App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
        useMaterial3: true,
      ),
      home: const NewsHomePage(),
    );
  }
}


12. Why StatefulWidget is Useful


The News App needs to update its interface when news data is loaded, when a search is performed, when a category changes, or when an error occurs. A simple implementation can use StatefulWidget to manage these changing states.


class NewsHomePage extends StatefulWidget {
  const NewsHomePage({super.key});

  @override
  State createState() =>
      _NewsHomePageState();
}


13. What is a News Article?


A news article is a structured piece of information containing fields such as title, description, image URL, author, publication date, source, and article URL.


A simplified article object can contain:











FieldPurpose
titleArticle headline
descriptionShort summary of the article
imageUrlArticle image
authorArticle author
publishedAtPublication date/time
sourceNews source name
urlOriginal article URL

14. Create the Article Model


A model class provides a structured representation of API data.


class Article {
  final String title;
  final String description;
  final String imageUrl;
  final String author;
  final String publishedAt;
  final String source;
  final String url;

  Article({
    required this.title,
    required this.description,
    required this.imageUrl,
    required this.author,
    required this.publishedAt,
    required this.source,
    required this.url,
  });

  factory Article.fromJson(
    Map json,
  ) {
    return Article(
      title: json['title'] ?? '',
      description: json['description'] ?? '',
      imageUrl: json['urlToImage'] ?? '',
      author: json['author'] ?? 'Unknown',
      publishedAt: json['publishedAt'] ?? '',
      source: json['source']?['name'] ?? 'Unknown',
      url: json['url'] ?? '',
    );
  }
}


15. Understanding JSON API Responses


News APIs commonly return JSON containing an object with an array of articles.


A simplified response may look like:


{
  "status": "ok",
  "totalResults": 2,
  "articles": [
    {
      "source": {
        "name": "Example News"
      },
      "author": "John Smith",
      "title": "Flutter Development Trends",
      "description": "A summary of the article.",
      "url": "https://example.com/article",
      "urlToImage": "https://example.com/image.jpg",
      "publishedAt": "2026-09-21T10:00:00Z"
    },
    {
      "source": {
        "name": "Example News"
      },
      "author": "Jane Smith",
      "title": "Mobile App Development",
      "description": "Another article summary.",
      "url": "https://example.com/article-2",
      "urlToImage": "https://example.com/image-2.jpg",
      "publishedAt": "2026-09-21T11:00:00Z"
    }
  ]
}

16. JSON Decoding


Dart provides the dart:convert library for decoding JSON strings.


import 'dart:convert';

final data = jsonDecode(response.body);


Flutter's documentation describes jsonDecode() as a straightforward approach for manually decoding JSON into Dart data structures. :contentReference[oaicite:2]{index=2}


17. Create the News Service


The NewsService class is responsible for communicating with the remote news API. Keeping API communication in a separate service makes the UI easier to maintain and test.


import 'dart:convert';
import 'package:http/http.dart' as http;

class NewsService {
  final String apiKey;

  NewsService(this.apiKey);

  Future> fetchNews() async {
    final uri = Uri.parse(
      'https://api.example.com/news'
      '?apiKey=$apiKey',
    );

    final response = await http.get(uri);

    if (response.statusCode == 200) {
      final data =
          jsonDecode(response.body)
              as Map;

      final articles =
          data['articles'] as List;

      return articles
          .map(
            (article) => Article.fromJson(
              article as Map,
            ),
          )
          .toList();
    }

    throw Exception('Failed to load news');
  }
}


The exact endpoint, query parameters, authentication method, and JSON structure depend on the news API provider you select.


18. HTTP GET Request


Most news-reading applications use GET requests to retrieve articles.


final response = await http.get(
  Uri.parse(url),
);

The http.get() method returns a Future containing an HTTP response. :contentReference[oaicite:3]{index=3}


19. Understanding async and await


Network operations take time and therefore should be handled asynchronously.


Future> fetchNews() async {
  final response = await http.get(
    Uri.parse(url),
  );

  // Process the response.
}


The async keyword allows a function to perform asynchronous operations, while await waits for a Future to complete before continuing that function.


20. API Authentication


Many news APIs require an API key or another authentication mechanism. The exact method depends on the provider.


Some APIs use a query parameter:


https://api.example.com/news?apiKey=YOUR_API_KEY

Other APIs use an HTTP authorization header:


final response = await http.get(
  Uri.parse(url),
  headers: {
    'Authorization': 'Bearer YOUR_API_TOKEN',
  },
);

Flutter's networking documentation shows how authorization information can be supplied through HTTP headers. :contentReference[oaicite:4]{index=4}


21. API Key Security


API credentials should be handled carefully. A credential embedded directly into a client application should not be assumed to be completely secret.


For learning projects, you may use a placeholder:


const String apiKey = 'YOUR_API_KEY';

For production applications, follow the news API provider's security guidance and consider a backend service when a credential must remain private.


22. Create Application State


class _NewsHomePageState
    extends State {
  final NewsService _newsService =
      NewsService('YOUR_API_KEY');

  List

_articles = [];
  bool _isLoading = false;
  String? _errorMessage;
}

These variables allow the application to represent loading, success, and error states.


23. Load News


Future _loadNews() async {
  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });

  try {
    final articles =
        await _newsService.fetchNews();

    setState(() {
      _articles = articles;
      _isLoading = false;
    });
  } catch (e) {
    setState(() {
      _isLoading = false;
      _errorMessage =
          'Unable to load news.';
    });
  }
}


24. Load News on Startup


The initial API request can be triggered from initState().


@override
void initState() {
  super.initState();
  _loadNews();
}

Do not place a network request directly inside build(), because build can be called repeatedly and could cause repeated requests.


25. Loading State


if (_isLoading)
  const Center(
    child: CircularProgressIndicator(),
  )

A loading indicator provides visual feedback while the API request is being processed.


26. Error State


if (_errorMessage != null)
  Center(
    child: Text(
      _errorMessage!,
      textAlign: TextAlign.center,
    ),
  )

It is better to show a clear user-friendly error message instead of displaying raw technical exceptions.


27. Display News with ListView.builder


ListView.builder(
  itemCount: _articles.length,
  itemBuilder: (context, index) {
    final article = _articles[index];

    return ArticleCard(
      article: article,
    );
  },
)


ListView.builder is useful for displaying dynamic collections because the list items are created as needed.


28. Create an Article Card


class ArticleCard extends StatelessWidget {
  final Article article;

  const ArticleCard({
    super.key,
    required this.article,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(
        horizontal: 12,
        vertical: 8,
      ),
      child: Column(
        crossAxisAlignment:
            CrossAxisAlignment.start,
        children: [
          if (article.imageUrl.isNotEmpty)
            Image.network(
              article.imageUrl,
              width: double.infinity,
              height: 200,
              fit: BoxFit.cover,
            ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment:
                  CrossAxisAlignment.start,
              children: [
                Text(
                  article.title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  article.description,
                  maxLines: 3,
                  overflow: TextOverflow.ellipsis,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}


29. Display Article Images


If the API provides an image URL, Flutter can display it using Image.network().


Image.network(
  article.imageUrl,
  width: double.infinity,
  height: 200,
  fit: BoxFit.cover,
)

Some articles may not contain an image. Always handle an empty or invalid image URL gracefully.


30. Image Error Handling


Image.network(
  article.imageUrl,
  width: double.infinity,
  height: 200,
  fit: BoxFit.cover,
  errorBuilder: (
    context,
    error,
    stackTrace,
  ) {
    return Container(
      height: 200,
      alignment: Alignment.center,
      child: const Icon(
        Icons.image_not_supported,
        size: 50,
      ),
    );
  },
)

31. Display Source Name


Text(
  article.source,
  style: const TextStyle(
    fontWeight: FontWeight.bold,
  ),
)

32. Display Publication Date


Text(
  article.publishedAt,
  style: const TextStyle(
    color: Colors.grey,
  ),
)

For a production application, the raw API timestamp can be parsed and formatted into a user-friendly date and time.


33. Display Author


Text(
  'By ${article.author}',
)

Because some news APIs may return a null author, the model should provide a fallback value such as Unknown.


34. Complete Article Card


class ArticleCard extends StatelessWidget {
  final Article article;

  const ArticleCard({
    super.key,
    required this.article,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(12),
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment:
            CrossAxisAlignment.start,
        children: [
          if (article.imageUrl.isNotEmpty)
            Image.network(
              article.imageUrl,
              width: double.infinity,
              height: 200,
              fit: BoxFit.cover,
              errorBuilder: (
                context,
                error,
                stackTrace,
              ) {
                return Container(
                  height: 200,
                  alignment: Alignment.center,
                  child: const Icon(
                    Icons.image_not_supported,
                    size: 50,
                  ),
                );
              },
            ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment:
                  CrossAxisAlignment.start,
              children: [
                Text(
                  article.source,
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  article.title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  article.description,
                  maxLines: 3,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 8),
                Text(
                  'By ${article.author}',
                  style: const TextStyle(
                    color: Colors.grey,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}


35. News Search


A search feature allows users to enter keywords such as Flutter, technology, sports, business, or science.


final TextEditingController _searchController =
    TextEditingController();

Create a search field:


TextField(
  controller: _searchController,
  textInputAction: TextInputAction.search,
  decoration: const InputDecoration(
    hintText: 'Search news',
    prefixIcon: Icon(Icons.search),
    border: OutlineInputBorder(),
  ),
  onSubmitted: (_) {
    _searchNews();
  },
)

36. Search API Method


Future _searchNews() async {
  final query =
      _searchController.text.trim();

  if (query.isEmpty) {
    return;
  }

  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });

  try {
    final articles =
        await _newsService.searchNews(query);

    setState(() {
      _articles = articles;
      _isLoading = false;
    });
  } catch (e) {
    setState(() {
      _isLoading = false;
      _errorMessage =
          'Unable to search news.';
    });
  }
}


37. Search Service Method


Future> searchNews(
  String query,
) async {
  final uri = Uri.parse(
    'https://api.example.com/news'
    '?q=${Uri.encodeQueryComponent(query)}'
    '&apiKey=$apiKey',
  );

  final response = await http.get(uri);

  if (response.statusCode != 200) {
    throw Exception('Search failed');
  }

  final data =
      jsonDecode(response.body)
          as Map;

  final articles =
      data['articles'] as List;

  return articles
      .map(
        (article) => Article.fromJson(
          article as Map,
        ),
      )
      .toList();
}


38. Category-Based News


News applications commonly provide categories such as:



  • General

  • Technology

  • Business

  • Sports

  • Entertainment

  • Health

  • Science


A category can be sent to the API as a query parameter according to the provider's API specification.


Future> fetchByCategory(
  String category,
) async {
  final uri = Uri.parse(
    'https://api.example.com/news'
    '?category=$category'
    '&apiKey=$apiKey',
  );

  final response = await http.get(uri);

  if (response.statusCode != 200) {
    throw Exception(
      'Failed to load category news',
    );
  }

  // Parse response here.
  return [];
}


39. Category Chips


SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: [
      'General',
      'Technology',
      'Business',
      'Sports',
      'Science',
      'Health',
    ].map(
      (category) {
        return Padding(
          padding: const EdgeInsets.only(
            right: 8,
          ),
          child: ChoiceChip(
            label: Text(category),
            selected:
                _selectedCategory == category,
            onSelected: (_) {
              _selectCategory(category);
            },
          ),
        );
      },
    ).toList(),
  ),
)

40. Store Selected Category


String _selectedCategory = 'General';

When a category is selected, update the state and request the appropriate data.


Future _selectCategory(
  String category,
) async {
  setState(() {
    _selectedCategory = category;
  });

  await _loadCategoryNews(category);
}


41. Pull-to-Refresh


RefreshIndicator can be used to allow users to pull down and refresh the latest news.


RefreshIndicator(
  onRefresh: _loadNews,
  child: ListView.builder(
    itemCount: _articles.length,
    itemBuilder: (context, index) {
      return ArticleCard(
        article: _articles[index],
      );
    },
  ),
)

42. Open an Article


A news application often allows users to open the original article in a browser or another supported application.


For this functionality, a URL-launching package can be added to the project.


flutter pub add url_launcher

Import the package:


import 'package:url_launcher/url_launcher.dart';

Then create a method:


Future _openArticle(
  String url,
) async {
  final uri = Uri.parse(url);

  if (await canLaunchUrl(uri)) {
    await launchUrl(
      uri,
      mode: LaunchMode.externalApplication,
    );
  }
}


43. Article Details Page


Instead of immediately opening the browser, the application can first show a dedicated article details screen.


class ArticleDetailsPage
    extends StatelessWidget {
  final Article article;

  const ArticleDetailsPage({
    super.key,
    required this.article,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Article'),
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment:
                CrossAxisAlignment.start,
            children: [
              Text(
                article.title,
                style: const TextStyle(
                  fontSize: 26,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 12),
              Text(article.source),
              const SizedBox(height: 16),
              if (article.imageUrl.isNotEmpty)
                Image.network(
                  article.imageUrl,
                ),
              const SizedBox(height: 16),
              Text(
                article.description,
                style: const TextStyle(
                  fontSize: 17,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}


44. Navigate to Article Details


Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) {
      return ArticleDetailsPage(
        article: article,
      );
    },
  ),
);

45. Handle Empty Results


The API may return zero articles for a search query.


if (_articles.isEmpty && !_isLoading) {
  return const Center(
    child: Text(
      'No articles found.',
    ),
  );
}

46. Handle API Errors











Status CodePossible MeaningApplication Response
200Request successfulParse and display articles
400Invalid requestCheck parameters
401Authentication problemCheck API credentials
403Access deniedCheck API permissions
404Resource not foundShow appropriate message
429Too many requestsHandle rate limiting
500Server errorAllow retry

47. Network Error Handling


try {
  final articles =
      await _newsService.fetchNews();

  setState(() {
    _articles = articles;
  });
} catch (e) {
  setState(() {
    _errorMessage =
        'Check your internet connection and try again.';
  });
}


User-facing error messages should be simple and actionable.


48. Network Timeout


A timeout can prevent the application from waiting indefinitely for a server response.


final response = await http
    .get(Uri.parse(url))
    .timeout(
      const Duration(seconds: 10),
    );

49. FutureBuilder Approach


FutureBuilder is another approach for representing the state of an asynchronous request.


FutureBuilder>(
  future: newsFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState ==
        ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    if (snapshot.hasError) {
      return Center(
        child: Text(
          'Error: ${snapshot.error}',
        ),
      );
    }

    final articles =
        snapshot.data ?? [];

    if (articles.isEmpty) {
      return const Center(
        child: Text('No articles found.'),
      );
    }

    return ListView.builder(
      itemCount: articles.length,
      itemBuilder: (context, index) {
        return ArticleCard(
          article: articles[index],
        );
      },
    );
  },
)


FutureBuilder can simplify UI handling for loading, success, and error states when working with a Future.


50. Complete News Home Page


class NewsHomePage extends StatefulWidget {
  const NewsHomePage({super.key});

  @override
  State createState() =>
      _NewsHomePageState();
}

class _NewsHomePageState
    extends State {
  final NewsService _newsService =
      NewsService('YOUR_API_KEY');

  final TextEditingController _searchController =
      TextEditingController();

  List

_articles = [];
  bool _isLoading = false;
  String? _errorMessage;
  String _selectedCategory = 'General';

  @override
  void initState() {
    super.initState();
    _loadNews();
  }

  Future _loadNews() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final articles =
          await _newsService.fetchNews();

      setState(() {
        _articles = articles;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
        _errorMessage =
            'Unable to load news.';
      });
    }
  }

  Future _searchNews() async {
    final query =
        _searchController.text.trim();

    if (query.isEmpty) {
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final articles =
          await _newsService.searchNews(query);

      setState(() {
        _articles = articles;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
        _errorMessage =
            'Unable to search news.';
      });
    }
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('News App'),
        centerTitle: true,
        actions: [
          IconButton(
            onPressed: _loadNews,
            icon: const Icon(Icons.refresh),
          ),
        ],
      ),
      body: SafeArea(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(12),
              child: TextField(
                controller: _searchController,
                textInputAction:
                    TextInputAction.search,
                decoration: InputDecoration(
                  hintText: 'Search news',
                  prefixIcon:
                      const Icon(Icons.search),
                  suffixIcon: IconButton(
                    onPressed: _searchNews,
                    icon: const Icon(Icons.send),
                  ),
                  border:
                      const OutlineInputBorder(),
                ),
                onSubmitted: (_) {
                  _searchNews();
                },
              ),
            ),
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              padding: const EdgeInsets.symmetric(
                horizontal: 12,
              ),
              child: Row(
                children: [
                  'General',
                  'Technology',
                  'Business',
                  'Sports',
                  'Science',
                  'Health',
                ].map(
                  (category) {
                    return Padding(
                      padding:
                          const EdgeInsets.only(
                        right: 8,
                      ),
                      child: ChoiceChip(
                        label: Text(category),
                        selected:
                            _selectedCategory ==
                                category,
                        onSelected: (_) {
                          setState(() {
                            _selectedCategory =
                                category;
                          });
                        },
                      ),
                    );
                  },
                ).toList(),
              ),
            ),
            const SizedBox(height: 8),
            if (_isLoading)
              const LinearProgressIndicator(),
            if (_errorMessage != null)
              Padding(
                padding:
                    const EdgeInsets.all(16),
                child: Text(
                  _errorMessage!,
                  textAlign: TextAlign.center,
                ),
              ),
            Expanded(
              child: RefreshIndicator(
                onRefresh: _loadNews,
                child: _articles.isEmpty
                    ? ListView(
                        children: const [
                          SizedBox(height: 150),
                          Center(
                            child: Text(
                              'No articles found.',
                            ),
                          ),
                        ],
                      )
                    : ListView.builder(
                        itemCount: _articles.length,
                        itemBuilder:
                            (context, index) {
                          return ArticleCard(
                            article:
                                _articles[index],
                          );
                        },
                      ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


51. Widget Tree


MaterialApp
    │
    └── NewsHomePage
        │
        └── Scaffold
            ├── AppBar
            │   ├── Title
            │   └── Refresh Button
            │
            └── Body
                └── Column
                    ├── Search TextField
                    ├── Category Chips
                    ├── Loading Indicator
                    └── Expanded
                        └── RefreshIndicator
                            └── ListView.builder
                                └── ArticleCard
                                    ├── Image
                                    ├── Source
                                    ├── Title
                                    ├── Description
                                    └── Author

52. News App State Flow


Initial State
      ↓
Load News
      ↓
Loading
      ↓
 ┌───────────────┐
 │               │
Success         Error
 │               │
 ↓               ↓
Articles       Error Message
 │
 ↓
Display UI
      ↓
Search / Category
      ↓
New API Request
      ↓
Updated Articles

53. Pagination


A real news application can contain a large number of articles. Loading everything at once may not be efficient. Pagination allows the application to request articles in smaller groups.


Page 1
  ↓
Articles 1-20
  ↓
Page 2
  ↓
Articles 21-40
  ↓
Page 3
  ↓
Articles 41-60

A page parameter can be sent to the API according to the provider's documentation.


final uri = Uri.parse(
  'https://api.example.com/news'
  '?page=2'
  '&pageSize=20'
  '&apiKey=$apiKey',
);

54. Infinite Scrolling


Infinite scrolling can automatically request the next page when the user reaches the bottom of the article list.


if (scrollController.position.pixels >
    scrollController.position.maxScrollExtent - 200) {
  _loadMoreArticles();
}

A ScrollController can be used to monitor the scroll position.


55. Search Debouncing


If the application searches automatically while the user types, sending a network request for every keystroke can create unnecessary traffic. Debouncing waits for a short period after the user's last input before making the request.


Timer? _debounce;

void onSearchChanged(String value) {
  _debounce?.cancel();

  _debounce = Timer(
    const Duration(milliseconds: 500),
    () {
      _searchNews();
    },
  );
}


Remember to dispose or cancel timers appropriately when the widget is removed.


56. Favorites


Users can be allowed to save articles as favorites.


final Set favoriteUrls = {};

void toggleFavorite(Article article) {
  setState(() {
    if (favoriteUrls.contains(article.url)) {
      favoriteUrls.remove(article.url);
    } else {
      favoriteUrls.add(article.url);
    }
  });
}


57. Bookmark Feature


A bookmark feature can store selected articles for later reading. For permanent storage, use an appropriate local persistence solution rather than only keeping bookmarks in memory.


58. Dark Mode


A News App can provide light and dark themes.


ThemeData(
  brightness: Brightness.light,
)

and:


ThemeData(
  brightness: Brightness.dark,
)

59. Offline Caching


If the application stores previously downloaded articles locally, it can show cached content when a network connection is unavailable.


Internet
   ↓
News API
   ↓
Fresh Articles
   ↓
Local Cache
   ↓
Flutter UI

No Internet
   ↓
Local Cache
   ↓
Previously Loaded Articles
   ↓
Flutter UI


60. Image Caching


News applications frequently load many images. An appropriate image-caching strategy can reduce repeated network downloads and improve the user experience.


61. Responsive News UI


The News App should work on phones, tablets, and larger screens.


Mobile
  ↓
Single-column article list

Tablet
  ↓
Two-column article layout

Desktop
  ↓
Multi-column news layout


A responsive layout can be created using LayoutBuilder, GridView, or other responsive widgets.


62. GridView for Tablet/Desktop


GridView.builder(
  gridDelegate:
      const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 1.2,
  ),
  itemCount: _articles.length,
  itemBuilder: (context, index) {
    return ArticleCard(
      article: _articles[index],
    );
  },
)

63. Performance Considerations



  • Use ListView.builder for large dynamic lists.

  • Avoid unnecessary API requests.

  • Use pagination for large result sets.

  • Cache images where appropriate.

  • Do not perform expensive work inside build().

  • Move expensive JSON parsing to a background isolate when the response is large enough to cause UI jank.

  • Reuse widgets where practical.


Flutter's networking documentation notes that expensive JSON parsing can be moved to a background isolate using compute() when necessary. :contentReference[oaicite:5]{index=5}


64. Background JSON Parsing


For very large API responses, JSON parsing can become expensive. Flutter provides mechanisms such as compute() to move expensive parsing work to a background isolate.


List
parseArticles(
  String responseBody,
) {
  final data =
      jsonDecode(responseBody)
          as Map;

  final articles =
      data['articles'] as List;

  return articles
      .map(
        (item) => Article.fromJson(
          item as Map,
        ),
      )
      .toList();
}


65. API Testing


Before connecting the API to Flutter, test the endpoint independently to confirm that:



  • The URL is correct.

  • The API key is valid.

  • The query parameters are correct.

  • The response status is successful.

  • The JSON structure matches your model.

  • Required fields are available.


66. Debugging API Requests


When an API request does not work, inspect:



  • API endpoint

  • API key

  • HTTP method

  • Request parameters

  • Request headers

  • Status code

  • Response body

  • JSON structure

  • Internet permission

  • Network connectivity


Flutter DevTools provides a Network View that can inspect HTTP, HTTPS, and WebSocket traffic from Flutter and Dart applications. :contentReference[oaicite:6]{index=6}


67. Common Errors











ErrorPossible CauseSolution
401 UnauthorizedInvalid API credentialsCheck the API key or authentication method
403 ForbiddenAccess deniedCheck API permissions and plan limits
404 Not FoundIncorrect endpointVerify the API URL
429 Too Many RequestsRate limit exceededReduce requests or follow provider limits
JSON parsing errorUnexpected response structureInspect and update the model
Image not displayedInvalid or empty image URLUse fallback UI
No internetNetwork unavailableShow a retry/offline message

68. Common Flutter Mistakes



  • Forgetting to add the http package.

  • Forgetting Android Internet permission when required.

  • Calling an API directly from build().

  • Using an incorrect API endpoint.

  • Using an invalid API key.

  • Assuming all JSON fields are non-null.

  • Not checking HTTP status codes.

  • Not handling empty article lists.

  • Not handling missing article images.

  • Not showing a loading indicator.

  • Displaying raw exceptions to users.

  • Creating unnecessary API calls.

  • Not disposing TextEditingController or other controllers.


69. Best Practices



  • Separate API logic from UI code.

  • Create dedicated model classes.

  • Use a service class for network operations.

  • Use async/await for asynchronous operations.

  • Check HTTP status codes.

  • Handle loading, success, empty, and error states.

  • Validate search input.

  • Use reusable article widgets.

  • Use pagination for large news feeds.

  • Handle missing images.

  • Keep credentials out of source control.

  • Use meaningful class and variable names.

  • Test network failure scenarios.


70. Recommended Architecture


lib/
├── main.dart
├── models/
│   └── article.dart
├── services/
│   └── news_service.dart
├── screens/
│   ├── home_page.dart
│   └── article_details_page.dart
├── widgets/
│   ├── article_card.dart
│   ├── category_chip.dart
│   └── loading_view.dart
└── utils/
    └── constants.dart

71. Separation of Responsibilities









LayerResponsibility
ModelRepresents article data
ServiceCommunicates with the news API
ScreenDisplays application pages
WidgetProvides reusable UI components
State LayerManages loading, success, search, and error states

72. MVVM-Style Architecture


As the project becomes larger, an architecture such as Model-View-ViewModel can separate data operations, UI, and state management. Flutter's learning materials use MVVM to demonstrate separation of concerns for network-connected applications. :contentReference[oaicite:7]{index=7}


Model
  ↓
Handles API/Data
  ↓
ViewModel
  ↓
Manages State
  ↓
View
  ↓
Displays UI

73. Complete Project Flow


Create Flutter Project
        ↓
Add HTTP Package
        ↓
Configure Internet Access
        ↓
Select News API
        ↓
Create Article Model
        ↓
Create News Service
        ↓
Create Home Screen
        ↓
Fetch News
        ↓
Decode JSON
        ↓
Create Article Objects
        ↓
Display Article Cards
        ↓
Add Search
        ↓
Add Categories
        ↓
Add Article Details
        ↓
Add Refresh
        ↓
Handle Errors
        ↓
Test Application
        ↓
Add Advanced Features

74. Testing the News App


Test the application using the following scenarios:



  1. Launch the application.

  2. Verify that the loading indicator appears.

  3. Verify that articles are displayed after a successful response.

  4. Verify article titles.

  5. Verify article descriptions.

  6. Verify article images.

  7. Verify source names.

  8. Search for a keyword.

  9. Search with an empty field.

  10. Select different categories.

  11. Refresh the article list.

  12. Test invalid API credentials.

  13. Test without an internet connection.

  14. Test an empty API response.

  15. Test missing article images.

  16. Open an article details page.

  17. Test different screen sizes.


75. Practical Features to Add















FeatureDescription
SearchSearch news using keywords
CategoriesFilter news by topic
FavoritesSave preferred articles
BookmarksSave articles for later
PaginationLoad articles page by page
RefreshFetch latest articles
Dark ModeProvide light and dark themes
Offline CacheShow previously loaded content
NotificationsNotify users about selected news
Article DetailsShow detailed article information
Responsive UISupport phones, tablets, and larger screens

76. Interview Questions


Q1. What is a REST API?


A REST API is a web service interface that commonly uses HTTP methods and resource-oriented URLs to exchange data between applications.


Q2. Why is the http package used in Flutter?


The http package provides convenient methods for making HTTP requests such as GET, POST, PUT, and DELETE.


Q3. What is JSON?


JSON is a structured text format commonly used to exchange data between an application and a web service.


Q4. What does jsonDecode() do?


jsonDecode() converts a JSON string into Dart data structures such as maps and lists.


Q5. Why use a model class?


A model class provides a structured and typed representation of API data, making the rest of the application easier to understand and maintain.


Q6. What is Future in Dart?


A Future represents a value or error that will become available after an asynchronous operation completes.


Q7. Why should API calls not be made directly inside build()?


The build() method can execute multiple times. Calling an API directly from build() can therefore result in unnecessary repeated requests.


Q8. What is FutureBuilder?


FutureBuilder is a Flutter widget that builds its UI based on the state of a Future.


Q9. How can you search news?


The app can accept a keyword from a TextField and send that keyword to a news API as a query parameter.


Q10. How can you handle API errors?


Check HTTP status codes, catch exceptions, and display appropriate user-friendly error messages.


Q11. How can a News App support many articles?


Use pagination, efficient list widgets, image caching, and appropriate data loading strategies.


Q12. How can API authentication be implemented?


Depending on the API provider, credentials can be supplied using query parameters, authorization headers, or another supported authentication mechanism.


77. Learning Outcomes


After completing this project, you will have practical experience building an API-driven Flutter application. You will understand how Flutter communicates with a remote server, how JSON data is converted into Dart objects, how asynchronous operations are handled, and how dynamic API data is displayed using Flutter widgets.


You will also have a foundation for building larger applications such as finance apps, sports apps, e-commerce applications, weather applications, social applications, and content platforms.


78. Final Summary



  • A Flutter News App is an excellent project for learning API integration.

  • The http package can be used to communicate with a REST API.

  • News APIs commonly return JSON data.

  • jsonDecode() can be used to decode JSON.

  • Model classes make API data easier to work with.

  • async/await is useful for asynchronous network requests.

  • ListView.builder is useful for displaying dynamic news lists.

  • Search functionality can be implemented using API query parameters.

  • Categories can be used to filter news.

  • Loading, empty, success, and error states should be handled.

  • Pagination can improve performance when working with large article feeds.

  • API credentials should be handled carefully.

  • The application can be expanded with favorites, bookmarks, notifications, caching, and dark mode.




79. Learn Flutter


JustAcademy Flutter Training Course


Register for Flutter Course Demo


whatsapp