Flutter - ListVew - Search Filter - NewsApplication

Hello Guys, In this post we are going to learn how to integrate Network calls in Flutter

Here i am using the NewsAPI to fetch the news headlines. News API is a simple HTTP REST API for searching and retrieving live articles from all over the web

to Call api in Flutter we need to add 
http: ^0.12.0+2 dependencies in the pubspec.yaml file

 

Now comming to dart classes
Create a RouteSetting Page to handle the Navigation B/W different screens.

routes.dart

import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:newsapp/ui/categorylist.dart';
import 'package:newsapp/ui/newslist.dart';
import 'package:newsapp/ui/splash.dart';

class RouteSettngsPage extends RouteSettings{

  static RoutegenerateRoute(RouteSettings settings){
    var args=settings.arguments;
    switch(settings.name){
      case "/":
        return MaterialPageRoute(builder: (_)=>SplashPage());
        break;
      case "/category":
        return MaterialPageRoute(builder: (_)=>NewsCategory());
        break;
      case "/news":
        HashMapreq=args as HashMap;
        return MaterialPageRoute(builder: (_)=>News(req['category']));

        break;
    };
  }
}

 

Splash Screen

splash.dart

import 'package:flutter/material.dart';

class SplashPage extends StatefulWidget{
  @override
  State createState() {
    // TODO: implement createState
    return SplashState();
  }


}
class SplashState extends State{
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    Future.delayed(Duration(seconds: 3),(){
      Navigator.pushReplacementNamed(context, "/category");
    });
  }
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Container(

          //child: Future.delayed(Duration(seconds: 2),(){});
          child: Image.asset("splash_img.png",fit: BoxFit.cover,),

        ));
  }
}

here we added background image in the splash screen. for this we need to

add in assets folder and configure the path in pubspec.yaml file under assets path.

Future.delayed widget is used to load the Next screen after some time.

here have navigation the next screen after 3 seconds

Future.delayed(Duration(seconds: 3),() {

Navigator.pushReplacementNamed(context, "/category");

});

With PushReplacementNamed function of Navigato class.

 

News Catergory List

categorylist.dart

import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:newsapp/colors.dart';
import 'package:newsapp/model/newscategory.dart';


class NewsCategory extends StatefulWidget{
  @override
  State createState() {
    // TODO: implement createState
    return  NewsCategorystate();
  }

}
class NewsCategorystate extends State{
  static const  _appPrimaryValue = Color(0xFF009688);
  ListlistNews;
  ListlistNewsAll;
  Icon _searchIcon = new Icon(Icons.search);
  Widget _appBarTitle = new Text( 'Search Example' );
  TextEditingController searchController=new TextEditingController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    listNews=new List();
    listNewsAll=new List();
    fetchNews();
    _searchPressed();
    searchController.addListener((){
      print(searchController.text);
      if (searchController.text.isEmpty) {

        setState(() {
         listNews.clear();
         listNews.addAll(listNewsAll);

        });
      } else {
        setState(() {
          listNews.clear();
         for(int k=0;k0)?ListView.builder(
          itemCount: listNews.length,
          itemBuilder: (ctx,index){
          return setNewsItem(listNews[index]);
        }):Center(child: CircularProgressIndicator(),),

    );
  }


  setNewsItem(NewsCategoryModel newsModel)
  {
    return Card(
      elevation: 5,
      child: Container(
        margin: EdgeInsets.all(5),
        padding: EdgeInsets.all(5),
        child: InkWell(
          onTap: (){
            HashMaphm=new HashMap();
            hm['category']=newsModel.category;

            Navigator.pushNamed(context, "/news",arguments: hm);
          },
          child: Row(
          children: [
          Container(
            height: 50,
            width: 50,
            decoration: ShapeDecoration(shape: CircleBorder(),
          color: Colors.green[700]),child: Center(
          child: Text(newsModel.name.substring(0,1).toUpperCase(),
         style: TextStyle(fontSize: 30,color: Colors.white),)),
          ),
          SizedBox(width:15,),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(newsModel.name,style: TextStyle(fontSize: 18,
               color: Colors.black),),
                  SizedBox(height:5,),
                  Text(newsModel.description,style: TextStyle(fontSize: 12,
            color: Colors.grey[800]),maxLines: 3,),
                ],
              ),
            )
      ],),
        ),),
    );
  }


  fetchNews(){
    var callNews = NewsCategoryModel.callNewsCAtegory();
    callNews.then((data){
      var response=json.decode(data.body );
      var listNewsdata=response['sources']as List;
      setState(() {
        listNews=listNewsdata.map((model)=>NewsCategoryModel
.fromJson(model))
.toList();
        listNewsAll.clear();
        listNewsAll.addAll(listNews);
      });


    },onError: (error){
      print("Result Error $error");
    }
    );

  }
  void _searchPressed() {
    setState(() {
      if (this._searchIcon.icon == Icons.search) {
        this._searchIcon = new Icon(Icons.close);
        this._appBarTitle = new TextField(
          controller: searchController,
          decoration: new InputDecoration(
              prefixIcon: new Icon(Icons.search),
              hintText: 'Search...'
          ),
        );
      } else {
        this._searchIcon = new Icon(Icons.search);
        this._appBarTitle = new Text('Search Example');

        searchController.clear();
      }
    });
  }
}
 

 

Here i am calling the api in NewsCategoryModel file.

with  get('https://newsapi.org/v2/sources?apiKey=YOUR_NEWSAPI_KEY&page=1');

The NewsCategoryModel file looks like below

import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
class NewsCategoryModel {
  String id;
  String name;
  String description;
  String url;
  String category;
  String language;
  String country;

  NewsCategoryModel(this.id,this.name,this.description,this.url,
this.category,this.language,this.country);

  NewsCategoryModel.fromJson(MapparseModel)
  {
    id=parseModel['id'];
    name=parseModel['name'];
    description=parseModel['description'];
    url=parseModel['url'];
    category=parseModel['category'];
    language=parseModel['language'];
    country=parseModel['country'];
  }

  static Future callNewsCAtegory() async{
return  get('https://newsapi.org/v2/sources?apiKey=PUT_YOUR_NEWSAPI_KEY&page=1');
    //return  get('http://jsonplaceholder.typicode.com/photos');
  }
}

 in initState() method calling the API.

For the AppBar we are passing the childs dynamically based on Search Icon events.

 AppBar(title: _appBarTitle,
leading: IconButton(icon: this._searchIcon, onPressed: (){
  _searchPressed();
}),

 _appBarTitle & _searchIcon will modify on _searchPressed() function call.

For display each NewsCategory i have created card widget in setNewsItem() function

On Tap oneach Inkwell widget inside card will navigate to NewsList screen,

where we are passing the category name as argumnets to Navigator.pushedName like below

HashMaphm=new HashMap();
hm['category']=newsModel.category;

Navigator.pushNamed(context, "/news",arguments: hm);

 

The NewsList screen wich shows the news headlines for each category.

newslist.dart file

import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:newsapp/model/newsmodel.dart';

class News extends StatefulWidget{
  String category;
  News(this.category);

  @override
  State createState() {
    // TODO: implement createState
    return CategoryState();
  }

}
class CategoryState extends State{
  ListlistNews=[];
  bool hasPages=true;
  ScrollController _scrollController = new ScrollController();
  int page=1;
  @override
  void initState() {
    fetchnews();
    super.initState();
    _scrollController.addListener(() {
      if(!hasPages)
        {
          return;
        }
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        page=page+1;
        fetchnews();
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(title: Text(widget.category.toUpperCase()),),
      body: listNews.length>0?ListView.builder(
          itemCount: (hasPages)?listNews.length+1:listNews.length,
          itemBuilder: (ctx,index){
        if(index==listNews.length)
     return Center(child: Container(width:50,height: 50,
child: CircularProgressIndicator()));
        else
        return setNewsItem(listNews[index]);
      },controller: _scrollController,
      ):Center(child: CircularProgressIndicator(),),
    );
  }

  fetchnews(){
    var request=NewsModel.callNews(widget.category,page);
    request.then((data){

      var resdata=json.decode(data.body);
      String status=resdata['status'];

          hasPages=true;
          var listdata=resdata["articles"] as List;


          setState(() {
        if(status=="ok") {
          for (int k = 0; k < listdata.length; k++) {
            NewsModel myModel = NewsModel();
            myModel.author = listdata[k] ['author'];
            myModel.title = listdata[k] ['title'];
            myModel.description = listdata[k] ['description'];
            myModel.url = listdata[k] ['url'];
            myModel.urlToImage = listdata[k] ['urlToImage'];
            myModel.publishedAt = listdata[k] ['publishedAt'];
            myModel.content = listdata[k] ['content'];
            listNews.add(myModel);
          }
        }
        else
        {
          hasPages=false;
        }
          });



    },onError: (error){
      hasPages=false;
    });
  }

  setNewsItem(NewsModel newsModel)
  {
    return Card(
      elevation: 5,
      child: Container(
        margin: EdgeInsets.all(5),
        padding: EdgeInsets.all(5),
        child: InkWell(
          onTap: (){

          },
          child:  Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(newsModel.title,style: 
TextStyle(fontSize: 18,color: Colors.black),),
              SizedBox(height:5,),
             FadeInImage(placeholder: AssetImage("flutter_big.png"),
 height:200,fit: BoxFit.cover,
image: NetworkImage(newsModel.urlToImage,) ),
              SizedBox(height:5,),
              Text((newsModel.content==null)?"":newsModel.content,
style: TextStyle(fontSize: 12,color: Colors.grey[800]),maxLines: 3,),
            ],
          )
        ),),
    );
  }
}

 

The Routing the each page is handle from main.dart file with

MaterialApp widget onGenerateRoute property.

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      onGenerateRoute: RouteSettngsPage.generateRoute,
    );
  }
  
}

You can find complete code at  rrtutors github account