Crear Una Aplicación De Noticias Con Flutter

¿Eres alguien a quien le gusta leer el periódico de la mañana mientras estás sentado en la mesa del desayuno y bebes un té o un café? Bueno, yo soy una de esas personas a las que les encanta leer las noticias a primera hora de la mañana para estar al día.

¿Por qué no crear una aplicación para mantenernos actualizados con las últimas noticias seleccionadas para nosotros mismos? Apliquemos nuestro conocimiento de Flutter para crear nuestra propia aplicación de noticias. Estos son los pasos que seguiremos para crear la aplicación y mejorar nuestras habilidades de Flutter:

Siga leyendo para ver cómo será nuestro producto final.

Introducción a la aplicación de demostración

Estamos creando una aplicación de pantalla única que tiene una barra de búsqueda en la parte superior de la pantalla; un widget de carrusel para mostrar los principales titulares de todo el mundo; y una lista de las noticias basadas en País , Categoría o Canal cuando el usuario selecciona desde el cajón lateral.

Cuando ejecuta la aplicación, el País predeterminado para los principales titulares se establece en India y la Categoría predeterminada se establece en Negocios .

Aplicación de noticias terminada

Vamos a utilizar la clave API de NewsAPI.org . También puede usar la API de MediaStack y NewsData , ya que podemos consultar una cantidad limitada de artículos de noticias en modo desarrollador. La API de noticias permite alrededor de 100 consultas al día. Por el contrario, MediaStack permite 500 consultas en un mes y NewsData permite 200 consultas al día. Siempre puedes registrarte con diferentes cuentas para probar tu aplicación. Todo seguirá igual excepto la clave API única.

Con eso en mente, comencemos a construir.

Configuración de dependencias

En su pubspec.yamlarchivo, agregue las siguientes dependencias:

  • http: ^0.13.4 : una biblioteca componible basada en el futuro para realizar solicitudes HTTP. Usando este paquete, consultaremos artículos de noticias de NewsApi.org
  • webview_flutter: ^3.0.4 : un complemento de Flutter que proporciona un widget WebView en Android e iOS. Con este paquete, un usuario puede leer la noticia completa
  • carousel_slider: ^4.1.1 : un widget de control deslizante de carrusel que admite desplazamiento infinito y un widget secundario personalizado. Estamos utilizando este paquete para mostrar los últimos titulares que se desplazarán horizontalmente automáticamente
  • obtenga: ^4.6.5 — nuestra solución de administración de estado. He hablado de las ventajas de usar GetX como solución de gestión de estados para Flutter; es rápido y fácil de implementar, especialmente cuando un desarrollador está creando prototipos

Configuración de WebView para Android e iOS

Dado que estamos utilizando el paquete WebView para mostrar el artículo de noticias completo, debemos realizar algunos cambios en la aplicación/ build.gradlearchivo de Android y el archivo de iOS info.plistdentro de la Runnercarpeta.

Para Android

Tienes que cambiar el minSdkVersiona al menos 19. Además, agregue multiDexsoporte a las dependencias de Android. Por favor, mire la imagen de abajo como referencia:

Versión mínima de SDK

para iOS

Dentro de la Runnercarpeta, debe agregar esta línea para admitir vistas incrustadas para Flutter cuando se ejecuta en dispositivos iOS:

<key>io.flutter.embedded_views_preview</key>
   <string>YES</string>

Por favor, mire la imagen de abajo como referencia:

Vistas incrustadas

Nuestras dependencias están configuradas para la aplicación de noticias. Ahora, registrémonos en NewsApi.org y obtengamos nuestra clave API única.

Adquirir la clave de la API de noticias

Vaya a NewsAPI.org y regístrese con su ID de correo electrónico y contraseña. Tan pronto como se registre, generará una clave API única para usted que usaremos para solicitar artículos de noticias. Guarde esa clave como una constante en el proyecto Flutter.

Puntos finales

Para usar esta API, debemos comprender qué es un punto final. Un punto final es un punto distinto en el que una API permite que el software o los programas se comuniquen entre sí.

Es importante tener en cuenta que los puntos finales y las API no son lo mismo. Un punto final es un componente de una API, mientras que una API es un conjunto de reglas que permite que dos piezas de software compartan un recurso para comunicarse entre sí. Los puntos finales son las ubicaciones de esos recursos, y una API usa URL para recuperar las respuestas solicitadas.

Los puntos finales principales de la API de noticias

  1. https://newsapi.org/v2/todo ? (esto busca todos los artículos publicados por más de 80,000 fuentes diferentes)
  2. https://newsapi.org/v2/top-headlines ? (Esto devuelve titulares de noticias de última hora según el país y la categoría)
  3. También hay un punto final menor que devuelve principalmente noticias de editores específicos (p. ej., BBC, ABC) https://newsapi.org/v2/top-headlines/sources ?

Con los puntos finales anteriores, debemos proporcionar la clave API a través de la cual se maneja la autenticación. Si la clave API no se agrega al final de la URL, estamos obligados a recibir un archivo 401 - Unauthorized HTTP error.

Entonces la URL se verá así:

https://newsapi.org/v2/everything?q=keyword&apiKey=APIKEY

La URL anterior devolverá una respuesta JSON que se verá así:

{
"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]"
},

Después de entender lo anterior, ahora comenzaremos a programar nuestra aplicación, comenzando con las clases modelo y luego nuestra Controllerclase GetX.

Escribiendo las clases del modelo

Tenemos 3 clases modelo.

  1. 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'],
     );
}
  1. 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(),
     );
}
  1. 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'],
   );
 }
}

Si observa el ejemplo de respuesta JSON anterior, las clases modelo se basan en él. Los nombres de las variables en las clases del modelo deben coincidir con los campos en la respuesta JSON.

Obtener datos con la clase GetXController

Aquí vamos a definir todas nuestras variables, métodos y funciones para recuperar tres tipos de noticias que son:

  1. Titulares principales
  2. Noticias según país, categoría y canal
  3. Noticias buscadas

Comience por definir e inicializar las 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

El siguiente paso es una función API para recuperar un objeto JSON para todos los artículos de noticias de News API. Con el método de respuesta HTTP, obtenemos datos de la URL y decodificamos el objeto JSON en un formato legible. Luego estamos verificando el estado de la respuesta.

Si el código de respuesta es 200, significa que el estado es correcto. Si la respuesta tiene algunos datos, se cargará en la lista, que finalmente se mostrará en la interfaz de usuario. Esta es la función para recuperar todas las noticias:

// 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();
 }
}

Y aquí hay una función para recuperar noticias de última hora:

// 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();
 }
}

A continuación, agregamos funciones para comunicar los puntos finales que discutimos anteriormente y para recibir respuestas personalizadas de la API. Necesitamos pasar una cadena de URL a las funciones anteriores, lo que haremos cuando lo llamemos en las siguientes.

Para obtener todas las noticias y novedades según una palabra clave de búsqueda:

// 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);
}

Para obtener noticias de última hora según el país seleccionado por el usuario:

// 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);
}

Por último, anule el onInitmétodo y llame a las dos funciones anteriores:

@override
void onInit() {
 scrollController = ScrollController()..addListener(_scrollListener);
 getAllNews();
 getBreakingNews();
 super.onInit();
}

Creación de un NewsCardwidget personalizado

A continuación, estamos creando un widget personalizado que se usará para mostrar la imagen, el título, la descripción y la URL del artículo de noticias que obtendremos de la API. Este widget se llamará en el constructor ListView en la pantalla principal:

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),
           ),
         )
       ],
     ),
   );
 }
}

Así se verá nuestro newsCardtestamento.

Boletín

Es posible que observe valores constantes en el código. Tengo la costumbre de crear archivos constantes en todos mis proyectos de Flutter, para definir colores, tamaños, decoraciones de campos de texto, etc. No agregaré esos archivos al artículo aquí, pero los encontrará en el repositorio de GitHub.

Agregar un widget de barra de búsqueda

Ahora estamos empezando a construir nuestra pantalla de inicio. En la parte superior de la pantalla, tenemos nuestro campo de texto de búsqueda. Cuando un usuario ingresa cualquier palabra clave, la API buscará entre miles de artículos de diferentes fuentes y lo mostrará en la pantalla con la ayuda del widget NewsCard:

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)),
       ),
     ],
   ),
 ),
),

Así se verá nuestra barra de búsqueda.

Barra de búsqueda de noticias

Agregar un widget de carrusel

El widget de carrusel mostrará los titulares principales o las últimas noticias de diferentes países cuando un usuario seleccione un país del cajón lateral. Este widget está envuelto GetBuilderpara que se reconstruya cada vez que se selecciona un nuevo país y se deben actualizar las noticias de última hora.

He configurado la opción de carrusel para reproducir automáticamente el control deslizante. Se desplazará horizontalmente automáticamente sin necesidad de que el usuario lo desplace. El widget Stack muestra la imagen de la noticia y encima el título de la noticia.

También agregué un banner en la esquina superior derecha que dice Top Headlines , que es algo similar al banner de depuración. El widget Stack está nuevamente envuelto con InkWell, y dentro de él hay una onTapfunción. Cuando un usuario hace clic en cualquier elemento de noticias, lo llevará a la pantalla WebView donde se mostrará el artículo de noticias completo al lector:

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(),
     );
   }),

Así se verá nuestro carrusel.

Carrusel de noticias

Agregar un widget de cajón lateral

El widget del cajón tiene tres menús desplegables para seleccionar el país , la categoría o el canal . Todo esto se traduce principalmente en fuentes que ya hemos discutido. Es un punto final menor proporcionado por New API para personalizar la recuperación de los artículos.

Cuando selecciona cualquiera de los anteriores en los menús desplegables, la selección del usuario se mostrará en el cajón lateral y el nombre del país se mostrará encima de los NewsCardelementos de la lista. Esta función se agrega especialmente para la creación de prototipos para que, como desarrolladores, sepamos que la API está devolviendo una respuesta de acuerdo con el código:

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()),
     ],
   ),
 );
}

Así se verá nuestro sideDrawertestamento.

cajón lateral

cajón lateral

Completando nuestra pantalla de inicio

A continuación, agregamos el NewsCardwidget que habíamos creado anteriormente debajo del widget de carrusel, que muestra todas las demás noticias según la selección del usuario en el cajón lateral. Si un usuario ingresa una palabra clave de búsqueda en el campo de texto de búsqueda, los artículos de noticias se mostrarán aquí.

Tenga en cuenta que el widget de carrusel solo muestra los principales titulares y las últimas noticias del país seleccionado; no se filtra por categoría o canal. Si un usuario selecciona una categoría o un canal, el widget de carrusel no se actualizará; solo NewsCardse actualizará el widget. Pero cuando un usuario selecciona un nuevo país, el widget de carrusel se actualizará junto con el NewsCardwidget.

Nuevamente, el NewsCardwidget está envuelto con GetX Buildery también el InkWellwidget:

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),
                   );
                 });
   }),

El SingleChildScrollViewes el widget principal para la pantalla de inicio como el cuerpo del archivo Scaffold. El appBartiene un botón de actualización, que borra todos los filtros y devuelve la aplicación a su estado original.

Vista predeterminada de Flash News

Adición de la pantalla WebView

La pantalla WebView es un widget con estado que muestra el artículo completo cuando un usuario hace clic en cualquiera de las noticias, ya sea desde el carrusel o NewsCard.

Aquí, tenemos que inicializar a WebViewControllercon una Completerclase. Una Completerclase es una forma de producir Futureobjetos y completarlos luego con un valor o un error. El Scaffoldcuerpo tiene la WebViewclase aprobada directamente. No hay appBaren esta pantalla para que no impida al lector leer el artículo completo:

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);
       });
     },
   ));
 }
}

Centro de noticias

Hacer una pantalla de inicio

Nombré mi aplicación Flash⚡News y diseñé la imagen de mi pantalla de bienvenida en Canva. La página de inicio aparece durante tres segundos y luego se desvía al usuario a la pantalla de inicio principal. Las pantallas de bienvenida son muy fáciles de implementar y recomiendo que todas las aplicaciones tengan una para una breve introducción.

Pantalla de inicio de noticias Flash

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')),
   );
 }
}

¡Eso es todo! Hemos completado nuestra aplicación. Hay algunos otros archivos de Dart como mencioné anteriormente que encontrará en mi enlace de GitHub a continuación.

He tratado de mantener la interfaz de usuario bastante ordenada. Todo el enfoque está en los artículos de noticias que son fáciles de encontrar y leer para los usuarios. La API devuelve más de cien artículos a la vez; si observa el código detenidamente, solo mostraremos algunas páginas. Nuevamente, se nos permite una cantidad limitada de consultas y algunos artículos a la vez se cargan más rápido.

Espero que esto le dé una idea general de cómo implementar la interacción entre los puntos finales de JSON, obtener datos de una API y mostrar esos datos en la pantalla.

¡Gracias!

Enlace al repositorio de Github: Flash News .

Fuente: https://blog.logrocket.com/building-news-app-flutter/

   #news #flutter 

What is GEEK

Buddha Community

Crear Una Aplicación De Noticias Con Flutter

Google's Flutter 1.20 stable announced with new features - Navoki

Flutter Google cross-platform UI framework has released a new version 1.20 stable.

Flutter is Google’s UI framework to make apps for Android, iOS, Web, Windows, Mac, Linux, and Fuchsia OS. Since the last 2 years, the flutter Framework has already achieved popularity among mobile developers to develop Android and iOS apps. In the last few releases, Flutter also added the support of making web applications and desktop applications.

Last month they introduced the support of the Linux desktop app that can be distributed through Canonical Snap Store(Snapcraft), this enables the developers to publish there Linux desktop app for their users and publish on Snap Store.  If you want to learn how to Publish Flutter Desktop app in Snap Store that here is the tutorial.

Flutter 1.20 Framework is built on Google’s made Dart programming language that is a cross-platform language providing native performance, new UI widgets, and other more features for the developer usage.

Here are the few key points of this release:

Performance improvements for Flutter and Dart

In this release, they have got multiple performance improvements in the Dart language itself. A new improvement is to reduce the app size in the release versions of the app. Another performance improvement is to reduce junk in the display of app animation by using the warm-up phase.

sksl_warm-up

If your app is junk information during the first run then the Skia Shading Language shader provides for pre-compilation as part of your app’s build. This can speed it up by more than 2x.

Added a better support of mouse cursors for web and desktop flutter app,. Now many widgets will show cursor on top of them or you can specify the type of supported cursor you want.

Autofill for mobile text fields

Autofill was already supported in native applications now its been added to the Flutter SDK. Now prefilled information stored by your OS can be used for autofill in the application. This feature will be available soon on the flutter web.

flutter_autofill

A new widget for interaction

InteractiveViewer is a new widget design for common interactions in your app like pan, zoom drag and drop for resizing the widget. Informations on this you can check more on this API documentation where you can try this widget on the DartPad. In this release, drag-drop has more features added like you can know precisely where the drop happened and get the position.

Updated Material Slider, RangeSlider, TimePicker, and DatePicker

In this new release, there are many pre-existing widgets that were updated to match the latest material guidelines, these updates include better interaction with Slider and RangeSliderDatePicker with support for date range and time picker with the new style.

flutter_DatePicker

New pubspec.yaml format

Other than these widget updates there is some update within the project also like in pubspec.yaml file format. If you are a flutter plugin publisher then your old pubspec.yaml  is no longer supported to publish a plugin as the older format does not specify for which platform plugin you are making. All existing plugin will continue to work with flutter apps but you should make a plugin update as soon as possible.

Preview of embedded Dart DevTools in Visual Studio Code

Visual Studio code flutter extension got an update in this release. You get a preview of new features where you can analyze that Dev tools in your coding workspace. Enable this feature in your vs code by _dart.previewEmbeddedDevTools_setting. Dart DevTools menu you can choose your favorite page embed on your code workspace.

Network tracking

The updated the Dev tools comes with the network page that enables network profiling. You can track the timings and other information like status and content type of your** network calls** within your app. You can also monitor gRPC traffic.

Generate type-safe platform channels for platform interop

Pigeon is a command-line tool that will generate types of safe platform channels without adding additional dependencies. With this instead of manually matching method strings on platform channel and serializing arguments, you can invoke native class and pass nonprimitive data objects by directly calling the Dartmethod.

There is still a long list of updates in the new version of Flutter 1.2 that we cannot cover in this blog. You can get more details you can visit the official site to know more. Also, you can subscribe to the Navoki newsletter to get updates on these features and upcoming new updates and lessons. In upcoming new versions, we might see more new features and improvements.

You can get more free Flutter tutorials you can follow these courses:

#dart #developers #flutter #app developed #dart devtools in visual studio code #firebase local emulator suite in flutter #flutter autofill #flutter date picker #flutter desktop linux app build and publish on snapcraft store #flutter pigeon #flutter range slider #flutter slider #flutter time picker #flutter tutorial #flutter widget #google flutter #linux #navoki #pubspec format #setup flutter desktop on windows

Terry  Tremblay

Terry Tremblay

1598396940

What is Flutter and why you should learn it?

Flutter is an open-source UI toolkit for mobile developers, so they can use it to build native-looking** Android and iOS** applications from the same code base for both platforms. Flutter is also working to make Flutter apps for Web, PWA (progressive Web-App) and Desktop platform (Windows,macOS,Linux).

flutter-mobile-desktop-web-embedded_min

Flutter was officially released in December 2018. Since then, it has gone a much stronger flutter community.

There has been much increase in flutter developers, flutter packages, youtube tutorials, blogs, flutter examples apps, official and private events, and more. Flutter is now on top software repos based and trending on GitHub.

Flutter meaning?

What is Flutter? this question comes to many new developer’s mind.

humming_bird_dart_flutter

Flutter means flying wings quickly, and lightly but obviously, this doesn’t apply in our SDK.

So Flutter was one of the companies that were acquired by **Google **for around $40 million. That company was based on providing gesture detection and recognition from a standard webcam. But later when the Flutter was going to release in alpha version for developer it’s name was Sky, but since Google already owned Flutter name, so they rename it to Flutter.

Where Flutter is used?

Flutter is used in many startup companies nowadays, and even some MNCs are also adopting Flutter as a mobile development framework. Many top famous companies are using their apps in Flutter. Some of them here are

Dream11

Dream11

NuBank

NuBank

Reflectly app

Reflectly app

Abbey Road Studios

Abbey Road Studios

and many more other apps. Mobile development companies also adopted Flutter as a service for their clients. Even I was one of them who developed flutter apps as a freelancer and later as an IT company for mobile apps.

Flutter as a service

#dart #flutter #uncategorized #flutter framework #flutter jobs #flutter language #flutter meaning #flutter meaning in hindi #google flutter #how does flutter work #what is flutter

Punith Raaj

1644991598

The Ultimate Guide To Tik Tok Clone App With Firebase - Ep 2

The Ultimate Guide To Tik Tok Clone App With Firebase - Ep 2
In this video, I'm going to show you how to make a Cool Tik Tok App a new Instagram using Flutter,firebase and visual studio code.

In this tutorial, you will learn how to Upload a Profile Pic to Firestore Data Storage.

🚀 Nice, clean and modern TikTok Clone #App #UI made in #Flutter⚠️

Starter Project : https://github.com/Punithraaj/Flutter_Tik_Tok_Clone_App/tree/Episode1

► Timestamps 
0:00 Intro 0:20 
Upload Profile Screen 
16:35 Image Picker
20:06 Image Cropper 
24:25 Firestore Data Storage Configuration.

⚠️ IMPORTANT: If you want to learn, I strongly advise you to watch the video at a slow speed and try to follow the code and understand what is done, without having to copy the code, and then download it from GitHub.

► Social Media 
GitHub: https://github.com/Punithraaj/Flutter_Tik_Tok_Clone_App.git
LinkedIn: https://www.linkedin.com/in/roaring-r...
Twitter: https://twitter.com/roaringraaj
Facebook: https://www.facebook.com/flutterdartacademy

► Previous Episode : https://youtu.be/QnL3fr-XpC4
► Playlist: https://youtube.com/playlist?list=PL6vcAuTKAaYe_9KQRsxTsFFSx78g1OluK

I hope you liked it, and don't forget to like,comment, subscribe, share this video with your friends, and star the repository on GitHub!
⭐️ Thanks for watching the video and for more updates don't forget to click on the notification. 
⭐️Please comment your suggestion for my improvement. 
⭐️Remember to like, subscribe, share this video, and star the repo on Github :)

Hope you enjoyed this video!
If you loved it, you can Buy me a coffee : https://www.buymeacoffee.com/roaringraaj

LIKE & SHARE & ACTIVATE THE BELL Thanks For Watching :-)
 
https://youtu.be/F_GgZVD4sDk

#flutter tutorial - tiktok clone with firebase #flutter challenge @tiktokclone #fluttertutorial firebase #flutter firebase #flutter pageview #morioh #flutter

Punith Raaj

1640672627

Flutter Hotel Booking UI - Book your Stay At A New Hotel With Flutter - Ep1

https://youtu.be/-tHUmjIkGJ4
Flutter Hotel Booking UI - Book your Stay At A New Hotel With Flutter - Ep1
#flutter #fluttertravelapp #hotelbookingui #flutter ui design 

In this video, I'm going to show you how to make a Cool Hotel Booking App using Flutter and visual studio code. 

In this tutorial, you will learn how to create a Splash Screen and Introduction Screen, how to implement a SmoothPageIndicator in Flutter. 

🚀 Nice, clean and modern Hotel Booking #App #UI made in #Flutter 

⚠️ IMPORTANT: If you want to learn, I strongly advise you to watch the video at a slow speed and try to follow the code and understand what is done, without having to copy the code, and then download it from GitHub. 

► Social Media 

    GitHub: https://github.com/Punithraaj 

    LinkedIn: https://www.linkedin.com/in/roaring-r...

    Twitter: https://twitter.com/roaringraaj

    Facebook: https://www.facebook.com/flutterdartacademy

I hope you liked it, and don't forget to like,comment, subscribe, share this video with your friends, and star the repository on GitHub! 

⭐️ Thanks for watching the video and for more updates don't forget to click on the notification.⭐️Please comment your suggestion for my improvement. ⭐️Remember to like, subscribe, share this video, and star the repo on Github :)Hope you enjoyed this video! If you loved it, you can Buy me a coffee : https://www.buymeacoffee.com/roaringraaj

#flutter riverpod #flutter travel app #appointment app flutter #morioh

Adobe XD plugin for Flutter with CodePen Tutorial

Recently Adobe XD releases a new version of the plugin that you can use to export designs directly into flutter widgets or screens. Yes, you read it right, now you can make and export your favorite design in Adobe XD and export all the design in the widget form or as a full-screen design, this can save you a lot of time required in designing.

What we will do?
I will make a simple design of a dialogue box with a card design with text over it as shown below. After you complete this exercise you can experiment with the UI. You can make your own components or import UI kits available with the Adobe XD.

#developers #flutter #adobe xd design export to flutter #adobe xd flutter code #adobe xd flutter code generator - plugin #adobe xd flutter plugin #adobe xd flutter plugin tutorial #adobe xd plugins #adobe xd to flutter #adobe xd tutorial #codepen for flutter.