Are you someone who likes to read the morning newspaper while sitting at your breakfast table and sipping on your tea or coffee? Well, I am one of those people that loves to read the news first thing in the morning to stay updated.
Why not build an app to stay updated with the latest curated news for ourselves? Let’s apply our knowledge of Flutter to create our own news app. Here are the steps we’ll take to create the app and improve our Flutter skills:
-
- Introduction to the demo application
- Setting up dependencies
- Configuring WebView for Android and iOS
- Acquiring the News API key
- Writing the model classes
- Fetching data with the GetX Controller class
- Creating a NewsCard widget
- Adding a search bar widget
- Adding a carousel widget
- Adding a side drawer widget
- Completing our home screen
Read on to see what our final product will look like.
Introduction to the demo application
We’re building a single-screen app that has a search bar at the top of the screen; a Carousel widget to display top headlines from around the world; and a list of the news based on Country, Category, or Channel when the user selects from the side drawer.
When you run the application, the default Country for top headlines is set to India, and the default Category is set to Business.
We are going to use the API key from NewsAPI.org. You can also use the API from MediaStack and NewsData since we can query a limited amount of news articles in developer mode. The News API allows around 100 queries a day. Conversely, MediaStack allows 500 queries in a month and NewsData allows 200 queries a day. You can always sign up with different accounts to test your application. Everything will remain the same except the unique API key.
With that in mind, let us start building.
Setting up dependencies
In your pubspec.yaml
file, please add the below dependencies:
- http: ^0.13.4 — a composable, Future-based library for making HTTP requests. Using this package we will query news articles from NewsApi.org
- webview_flutter: ^3.0.4 — a Flutter plugin that provides a WebView widget on Android and iOS. With this package, a user can read the whole news article
- carousel_slider: ^4.1.1 — a carousel slider widget that supports infinite scroll and a custom child widget. We are using this package to show the latest headlines that will automatically scroll horizontally
- get: ^4.6.5 — our state management solution. I have spoken about the advantages of using GetX as a state management solution for Flutter; it is fast and easy to implement, especially when a developer is prototyping
Configuring WebView for Android and iOS
Since we are using the WebView package to display the entire news article, we have to make a few changes to the Android app/build.gradle
file and iOS info.plist
file inside the Runner
folder.
For Android
You have to change the minSdkVersion
to at least 19
. Also, add multiDex
support to Android dependencies. Please look at the image below for reference:
For iOS
Inside the Runner
folder, you have to add this line to support embedded views for Flutter when running on iOS devices:
<key>io.flutter.embedded_views_preview</key> <string>YES</string>
Please look at the image below for reference:
Our dependencies are set up for the news app. Now, let’s sign up on NewsApi.org and get our unique API key.
Acquiring the News API key
Go to NewsAPI.org and sign up with your email ID and password. As soon as you sign up, it will generate a unique API key for yourself that we will use to request news articles. Save that key as a constant in the Flutter project.
Endpoints
To use this API, we need to understand what an endpoint is. An endpoint is a distinct point at which an API allows software or programs to communicate with each other.
It is important to note that endpoints and APIs are not the same things. An endpoint is a component of an API, whereas an API is a set of rules that allows two pieces of software to share a resource to communicate with each other. Endpoints are the locations of those resources, and an API uses URLs to retrieve the requested responses.
The News API’s main endpoints
- https://newsapi.org/v2/everything? (this searches for every article published by over 80,000 different sources)
- https://newsapi.org/v2/top-headlines? (this returns breaking news headlines according to country and category)
- There is also a minor endpoint that mainly returns news from specific publishers (eg: BBC, ABC) https://newsapi.org/v2/top-headlines/sources?
With the above endpoints, we have to provide the API key through which authentication is handled. If the API key is not appended at the end of the URL, we are bound to receive a 401 - Unauthorized HTTP error
.
So the URL will look something like this:
https://newsapi.org/v2/everything?q=keyword&apiKey=APIKEY
The above URL will return a JSON response that will look something like this:
{ "status": "ok", "totalResults": 9364, - "articles": [ - { - "source": { "id": "the-verge", "name": "The Verge" }, "author": "Justine Calma", "title": "Texas heatwave and energy crunch curtails Bitcoin mining", "description": "Bitcoin miners in Texas powered down to respond to an energy crunch triggered by a punishing heatwave. Energy demand from cryptomining is growing in the state.", "url": "https://www.theverge.com/2022/7/12/23205066/texas-heat-curtails-bitcoin-mining-energy-demand-electricity-grid", "urlToImage": "https://cdn.vox-cdn.com/thumbor/sP9sPjh-2PfK76HRsOfHNYNQWAo=/0x285:4048x2404/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/23761862/1235927096.jpg", "publishedAt": "2022-07-12T15:50:17Z", "content": "Miners voluntarily powered down as energy demand and prices spiked \r\nAn aerial view of the Whinstone US Bitcoin mining facility in Rockdale, Texas, on October 9th, 2021. The long sheds at North Ameri... [+3770 chars]" },
After understanding the above, we will now start programming our application, starting with the model classes and then our GetX Controller
class.
Writing the model classes
We have 3 model classes.
ArticleModel
class ArticleModel { ArticleModel(this.source, this.author, this.title, this.description, this.url, this.urlToImage, this.publishedAt, this.content); String? author, description, urlToImage, content; String title, url, publishedAt; SourceModel source; Map<String, dynamic> toJson() { return { 'author': author, 'description': description, 'urlToImage': urlToImage, 'content': content, 'title': title, 'url': url, 'publishedAt': publishedAt, 'source': source, }; } factory ArticleModel.fromJson(Map<String, dynamic> json) => ArticleModel( SourceModel.fromJson(json['source'] as Map<String, dynamic>), json['author'], json['title'], json['description'], json['url'], json['urlToImage'], json['publishedAt'], json['content'], ); }
NewsModel
class NewsModel { NewsModel(this.status, this.totalResults, this.articles); String status; int totalResults; List<ArticleModel> articles; Map<String, dynamic> toJson() { return { 'status': status, 'totalResults': totalResults, 'articles': articles, }; } factory NewsModel.fromJson(Map<String, dynamic> json) => NewsModel( json['status'], json['totalResults'], (json['articles'] as List<dynamic>) .map((e) => ArticleModel.fromJson(e as Map<String, dynamic>)) .toList(), ); }
SourceModel
class SourceModel { SourceModel({this.id = '', required this.name}); String? id, name; Map<String, dynamic> toJson() { return { 'id': id, 'name': name, }; } factory SourceModel.fromJson(Map<String, dynamic> json) { return SourceModel( id: json['id'], name: json['name'], ); } }
If you look at the example JSON response above, the model classes are based on it. Variable names in the model classes should match the fields in the JSON response.
Fetching data with the GetX Controller
class
Here we are going to define all our variables, methods, and functions to retrieve three types of news articles that are:
- Top headlines
- News according to country, category, and channel
- Searched news
Start by defining and initializing the variables:
// for list view List<ArticleModel> allNews = <ArticleModel>[]; // for carousel List<ArticleModel> breakingNews = <ArticleModel>[]; ScrollController scrollController = ScrollController(); RxBool articleNotFound = false.obs; RxBool isLoading = false.obs; RxString cName = ''.obs; RxString country = ''.obs; RxString category = ''.obs; RxString channel = ''.obs; RxString searchNews = ''.obs; RxInt pageNum = 1.obs; RxInt pageSize = 10.obs; String baseUrl = "https://newsapi.org/v2/top-headlines?"; // ENDPOINT
The next step is an API function to retrieve a JSON object for all news articles from the News API. Using the HTTP response method, we are getting data from the URL and decoding the JSON object into a readable format. Then we are checking for the response status.
If the response code is 200
, it means the status is okay. If the response has some data, it will be loaded to the list, which will eventually be shown in the UI. This is the function to retrieve all the news:
// function to retrieve a JSON response for all news from newsApi.org getAllNewsFromApi(url) async { //Creates a new Uri object by parsing a URI string. http.Response res = await http.get(Uri.parse(url)); if (res.statusCode == 200) { //Parses the string and returns the resulting Json object. NewsModel newsData = NewsModel.fromJson(jsonDecode(res.body)); if (newsData.articles.isEmpty && newsData.totalResults == 0) { articleNotFound.value = isLoading.value == true ? false : true; isLoading.value = false; update(); } else { if (isLoading.value == true) { // combining two list instances with spread operator allNews = [...allNews, ...newsData.articles]; update(); } else { if (newsData.articles.isNotEmpty) { allNews = newsData.articles; // list scrolls back to the start of the screen if (scrollController.hasClients) scrollController.jumpTo(0.0); update(); } } articleNotFound.value = false; isLoading.value = false; update(); } } else { articleNotFound.value = true; update(); } }
And here’s a function to retrieve breaking news:
// function to retrieve a JSON response for breaking news from newsApi.org getBreakingNewsFromApi(url) async { http.Response res = await http.get(Uri.parse(url)); if (res.statusCode == 200) { NewsModel newsData = NewsModel.fromJson(jsonDecode(res.body)); if (newsData.articles.isEmpty && newsData.totalResults == 0) { articleNotFound.value = isLoading.value == true ? false : true; isLoading.value = false; update(); } else { if (isLoading.value == true) { // combining two list instances with spread operator breakingNews = [...breakingNews, ...newsData.articles]; update(); } else { if (newsData.articles.isNotEmpty) { breakingNews = newsData.articles; if (scrollController.hasClients) scrollController.jumpTo(0.0); update(); } } articleNotFound.value = false; isLoading.value = false; update(); } } else { articleNotFound.value = true; update(); } }
Next, we add functions to communicate if the endpoints we discussed previously and to receive customized responses from the API. We need to pass a URL string to the above functions, which we will do when we call it in the ones below.
To get all news and news according to a search keyword:
// function to load and display all news and searched news on to UI getAllNews({channel = '', searchKey = '', reload = false}) async { articleNotFound.value = false; if (!reload && isLoading.value == false) { } else { country.value = ''; category.value = ''; } if (isLoading.value == true) { pageNum++; } else { allNews = []; pageNum.value = 2; } // ENDPOINT baseUrl = "https://newsapi.org/v2/top-headlines?pageSize=10&page=$pageNum&"; // default country is set to India baseUrl += country.isEmpty ? 'country=in&' : 'country=$country&'; // default category is set to Business baseUrl += category.isEmpty ? 'category=business&' : 'category=$category&'; baseUrl += 'apiKey=${NewsApiConstants.newsApiKey}'; // when a user selects a channel the country and category will become null if (channel != '') { country.value = ''; category.value = ''; baseUrl = "https://newsapi.org/v2/top-headlines?sources=$channel&apiKey=${NewsApiConstants.newsApiKey}"; } // when a enters any keyword the country and category will become null if (searchKey != '') { country.value = ''; category.value = ''; baseUrl = "https://newsapi.org/v2/everything?q=$searchKey&from=2022-07-01&sortBy=popularity&pageSize=10&apiKey=${NewsApiConstants.newsApiKey}"; } print(baseUrl); // calling the API function and passing the URL here getAllNewsFromApi(baseUrl); }
To get breaking news according to the country selected by the user:
// function to load and display breaking news on to UI getBreakingNews({reload = false}) async { articleNotFound.value = false; if (!reload && isLoading.value == false) { } else { country.value = ''; } if (isLoading.value == true) { pageNum++; } else { breakingNews = []; pageNum.value = 2; } // default language is set to English /// ENDPOINT baseUrl = "https://newsapi.org/v2/top-headlines?pageSize=10&page=$pageNum&languages=en&"; // default country is set to US baseUrl += country.isEmpty ? 'country=us&' : 'country=$country&'; //baseApi += category.isEmpty ? '' : 'category=$category&'; baseUrl += 'apiKey=${NewsApiConstants.newsApiKey}'; print([baseUrl]); // calling the API function and passing the URL here getBreakingNewsFromApi(baseUrl); }
Lastly, override the onInit
method and call the above two functions:
@override void onInit() { scrollController = ScrollController()..addListener(_scrollListener); getAllNews(); getBreakingNews(); super.onInit(); }
Creating a custom NewsCard
widget
Next, we are creating a custom widget that will be used to display the image, title, description, and URL of the news article that we will be getting from the API. This widget will be called in the ListView builder on the main screen:
class NewsCard extends StatelessWidget { final String imgUrl, title, desc, content, postUrl; const NewsCard( {Key? key, required this.imgUrl, required this.desc, required this.title, required this.content, required this.postUrl}); @override Widget build(BuildContext context) { return Card( elevation: Sizes.dimen_4, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(Sizes.dimen_10))), margin: const EdgeInsets.fromLTRB( Sizes.dimen_16, 0, Sizes.dimen_16, Sizes.dimen_16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: <Widget>[ ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(Sizes.dimen_10), topRight: Radius.circular(Sizes.dimen_10)), child: Image.network( imgUrl, height: 200, width: MediaQuery.of(context).size.width, fit: BoxFit.fill, // if the image is null errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(Sizes.dimen_10)), child: const SizedBox( height: 200, width: double.infinity, child: Icon(Icons.broken_image_outlined), ), ); }, )), vertical15, Padding( padding: const EdgeInsets.all(Sizes.dimen_6), child: Text( title, maxLines: 2, style: const TextStyle( color: Colors.black87, fontSize: Sizes.dimen_20, fontWeight: FontWeight.w500), ), ), Padding( padding: const EdgeInsets.all(Sizes.dimen_6), child: Text( desc, maxLines: 2, style: const TextStyle(color: Colors.black54, fontSize: Sizes.dimen_14), ), ) ], ), ); } }
This is how our newsCard
will look.
You might notice constant values in the code. I have a habit of creating constant files in all my Flutter projects, for defining color, sizes, textfield decorations, etc. I am not adding those files to the article here, but you will find those in the GitHub repository.
Adding a search bar widget
Now we are starting to build our home screen. At the top of the screen, we have our search text field. When a user enters any keyword, the API will search through thousands of articles from different sources and display it on the screen with the help of the NewsCard widget:
Flexible( child: Container( padding: const EdgeInsets.symmetric(horizontal: Sizes.dimen_8), margin: const EdgeInsets.symmetric( horizontal: Sizes.dimen_18, vertical: Sizes.dimen_16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(Sizes.dimen_8)), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( fit: FlexFit.tight, flex: 4, child: Padding( padding: const EdgeInsets.only(left: Sizes.dimen_16), child: TextField( controller: searchController, textInputAction: TextInputAction.search, decoration: const InputDecoration( border: InputBorder.none, hintText: "Search News"), onChanged: (val) { newsController.searchNews.value = val; newsController.update(); }, onSubmitted: (value) async { newsController.searchNews.value = value; newsController.getAllNews( searchKey: newsController.searchNews.value); searchController.clear(); }, ), ), ), Flexible( flex: 1, fit: FlexFit.tight, child: IconButton( padding: EdgeInsets.zero, color: AppColors.burgundy, onPressed: () async { newsController.getAllNews( searchKey: newsController.searchNews.value); searchController.clear(); }, icon: const Icon(Icons.search_sharp)), ), ], ), ), ),
This is how our search bar will look.
Adding a carousel widget
The carousel widget will display the top headlines or breaking news from different countries when a user selects a country from the side drawer. This widget is wrapped with GetBuilder
so that it gets rebuilt every time a new country is selected and breaking news needs to be updated.
I have set the carousel option to autoplay the slider. It will scroll horizontally automatically without the need for the user to scroll it. The Stack widget displays the image of the news and on top of it the title of the news.
I have also added a banner at the top right corner that says Top Headlines, which is something similar to the debug banner. The Stack widget is again wrapped with InkWell
, and inside it is an onTap
function. When a user clicks on any news item, it will take the user to the WebView screen where the whole news article will be displayed to the reader:
GetBuilder<NewsController>( init: NewsController(), builder: (controller) { return CarouselSlider( options: CarouselOptions( height: 200, autoPlay: true, enlargeCenterPage: true), items: controller.breakingNews.map((instance) { return controller.articleNotFound.value ? const Center( child: Text("Not Found", style: TextStyle(fontSize: 30))) : controller.breakingNews.isEmpty ? const Center(child: CircularProgressIndicator()) : Builder(builder: (BuildContext context) { try { return Banner( location: BannerLocation.topStart, message: 'Top Headlines', child: InkWell( onTap: () => Get.to(() => WebViewNews(newsUrl: instance.url)), child: Stack(children: [ ClipRRect( borderRadius: BorderRadius.circular(10), child: Image.network( instance.urlToImage ?? " ", fit: BoxFit.fill, height: double.infinity, width: double.infinity, // if the image is null errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { return Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 10)), child: const SizedBox( height: 200, width: double.infinity, child: Icon(Icons .broken_image_outlined), ), ); }, ), ), Positioned( left: 0, right: 0, bottom: 0, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular( 10), gradient: LinearGradient( colors: [ Colors.black12 .withOpacity(0), Colors.black ], begin: Alignment.topCenter, end: Alignment .bottomCenter)), child: Container( padding: const EdgeInsets .symmetric( horizontal: 5, vertical: 10), child: Container( margin: const EdgeInsets .symmetric( horizontal: 10), child: Text( instance.title, style: const TextStyle( fontSize: Sizes .dimen_16, color: Colors.white, fontWeight: FontWeight .bold), ))), )), ]), ), ); } catch (e) { if (kDebugMode) { print(e); } return Container(); } }); }).toList(), ); }),
This is how our carousel will look.
Adding a side drawer widget
The Drawer widget has three dropdowns for selecting the Country, Category, or Channel. All these mainly translate into sources that we have discussed already. It is a minor endpoint provided by New API to customize the retrieval of the articles.
When you select any of the above in dropdowns, the user’s selection will be displayed in the side drawer and the country name will be displayed above the NewsCard
list items. This feature is added specially for prototyping so that as developers we know that the API is returning a response according to the code:
Drawer sideDrawer(NewsController newsController) { return Drawer( backgroundColor: AppColors.lightGrey, child: ListView( children: <Widget>[ GetBuilder<NewsController>( builder: (controller) { return Container( decoration: const BoxDecoration( color: AppColors.burgundy, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(Sizes.dimen_10), bottomRight: Radius.circular(Sizes.dimen_10), )), padding: const EdgeInsets.symmetric( horizontal: Sizes.dimen_18, vertical: Sizes.dimen_18), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ controller.cName.isNotEmpty ? Text( "Country: ${controller.cName.value.capitalizeFirst}", style: const TextStyle( color: AppColors.white, fontSize: Sizes.dimen_18), ) : const SizedBox.shrink(), vertical15, controller.category.isNotEmpty ? Text( "Category: ${controller.category.value.capitalizeFirst}", style: const TextStyle( color: AppColors.white, fontSize: Sizes.dimen_18), ) : const SizedBox.shrink(), vertical15, controller.channel.isNotEmpty ? Text( "Category: ${controller.channel.value.capitalizeFirst}", style: const TextStyle( color: AppColors.white, fontSize: Sizes.dimen_18), ) : const SizedBox.shrink(), ], ), ); }, init: NewsController(), ), /// For Selecting the Country ExpansionTile( collapsedTextColor: AppColors.burgundy, collapsedIconColor: AppColors.burgundy, iconColor: AppColors.burgundy, textColor: AppColors.burgundy, title: const Text("Select Country"), children: <Widget>[ for (int i = 0; i < listOfCountry.length; i++) drawerDropDown( onCalled: () { newsController.country.value = listOfCountry[i]['code']!; newsController.cName.value = listOfCountry[i]['name']!.toUpperCase(); newsController.getAllNews(); newsController.getBreakingNews(); }, name: listOfCountry[i]['name']!.toUpperCase(), ), ], ), /// For Selecting the Category ExpansionTile( collapsedTextColor: AppColors.burgundy, collapsedIconColor: AppColors.burgundy, iconColor: AppColors.burgundy, textColor: AppColors.burgundy, title: const Text("Select Category"), children: [ for (int i = 0; i < listOfCategory.length; i++) drawerDropDown( onCalled: () { newsController.category.value = listOfCategory[i]['code']!; newsController.getAllNews(); }, name: listOfCategory[i]['name']!.toUpperCase()) ], ), /// For Selecting the Channel ExpansionTile( collapsedTextColor: AppColors.burgundy, collapsedIconColor: AppColors.burgundy, iconColor: AppColors.burgundy, textColor: AppColors.burgundy, title: const Text("Select Channel"), children: [ for (int i = 0; i < listOfNewsChannel.length; i++) drawerDropDown( onCalled: () { newsController.channel.value = listOfNewsChannel[i]['code']!; newsController.getAllNews( channel: listOfNewsChannel[i]['code']); }, name: listOfNewsChannel[i]['name']!.toUpperCase(), ), ], ), const Divider(), ListTile( trailing: const Icon( Icons.done_sharp, size: Sizes.dimen_28, color: Colors.black, ), title: const Text( "Done", style: TextStyle(fontSize: Sizes.dimen_16, color: Colors.black), ), onTap: () => Get.back()), ], ), ); }
This is how our sideDrawer
will look.
Completing our home screen
Next, we are adding the NewsCard
widget that we had created earlier below the carousel widget, which displays all the other news according to the user selection from the side drawer. If a user enters a search keyword in the search text field, the news articles will be displayed here.
Please note that the carousel widget only displays top headlines and breaking news from the selected country; it is not filtered according to the category or channel. If a user selects a category or a channel, the carousel widget will not get updated; only the NewsCard
widget will get updated. But when a user selects a new country, the carousel widget will get updated along with the NewsCard
widget.
Again, the NewsCard
widget is wrapped with GetX Builder
and as well the InkWell
widget:
GetBuilder<NewsController>( init: NewsController(), builder: (controller) { return controller.articleNotFound.value ? const Center( child: Text('Nothing Found'), ) : controller.allNews.isEmpty ? const Center(child: CircularProgressIndicator()) : ListView.builder( controller: controller.scrollController, physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: controller.allNews.length, itemBuilder: (context, index) { index == controller.allNews.length - 1 && controller.isLoading.isTrue ? const Center( child: CircularProgressIndicator(), ) : const SizedBox(); return InkWell( onTap: () => Get.to(() => WebViewNews( newsUrl: controller.allNews[index].url)), child: NewsCard( imgUrl: controller .allNews[index].urlToImage ?? '', desc: controller .allNews[index].description ?? '', title: controller.allNews[index].title, content: controller.allNews[index].content ?? '', postUrl: controller.allNews[index].url), ); }); }),
The SingleChildScrollView
is the parent widget for the home screen as the body of the Scaffold
. The appBar
has a refresh button, which clears all the filters and defaults the application to its original state.
Adding the WebView screen
The WebView screen is a stateful widget that displays the whole article when a user clicks on any of the news items either from the carousel or the NewsCard.
Here, we have to initialize a WebViewController
with a Completer
class. A Completer
class is a way to produce Future
objects and complete them later with a value or an error. The Scaffold
body has the WebView
class passed directly. There is no appBar
on this screen so that it does not obstruct the reader from reading the whole article:
class WebViewNews extends StatefulWidget { final String newsUrl; WebViewNews({Key? key, required this.newsUrl}) : super(key: key); @override State<WebViewNews> createState() => _WebViewNewsState(); } class _WebViewNewsState extends State<WebViewNews> { NewsController newsController = NewsController(); final Completer<WebViewController> controller = Completer<WebViewController>(); @override Widget build(BuildContext context) { return Scaffold( body: WebView( initialUrl: widget.newsUrl, javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { setState(() { controller.complete(webViewController); }); }, )); } }
Making a splash screen
I have named my app FlashNews and designed my splash screen image in Canva. The splash page pops up for three seconds and then the user is diverted to the main home screen. Splash screens are very easy to implement and I recommend all apps should have one for a short intro.
class SplashScreen extends StatefulWidget { const SplashScreen({Key? key}) : super(key: key); @override State<SplashScreen> createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> { @override void initState() { super.initState(); Timer(const Duration(seconds: 3), () { //navigate to home screen replacing the view Get.offAndToNamed('/homePage'); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.burgundy, body: Center(child: Image.asset('assets/flashNews.jpg')), ); } }
That is all! We have completed our application. There are a few other Dart files as I mentioned earlier that you will find on my GitHub link down below.
I have tried to keep the UI pretty neat. The whole focus is on news articles that are easy for the users to find and read. The API returns more than a hundred articles at a time; if you look at the code closely, we are only displaying a few pages from it. Again, we are allowed a limited amount of queries and a few articles at a time load more quickly.
Hope this gives you a gist of how to implement the interaction between JSON endpoints, fetch data from an API, and display that data on the screen.
Thank you!
Link to the Github repository: Flash News.
The post Building a news app with Flutter appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/NZi1QdO
Gain $200 in a week
via Read more