{"id":1010,"date":"2020-07-09T07:47:14","date_gmt":"2020-07-09T07:47:14","guid":{"rendered":"https:\/\/www.confianzit.com\/cit-blog\/?p=1010"},"modified":"2022-10-31T17:53:28","modified_gmt":"2022-10-31T17:53:28","slug":"flutter-paginated-lazy-loading-listview","status":"publish","type":"post","link":"https:\/\/www.confianzit.com\/cit-blog\/flutter-paginated-lazy-loading-listview\/","title":{"rendered":"Flutter: Paginated Lazy Loading ListView"},"content":{"rendered":"<p>[et_pb_section fb_built=&#8221;1&#8243; _builder_version=&#8221;3.22&#8243;][et_pb_row _builder_version=&#8221;4.9.4&#8243; background_size=&#8221;initial&#8221; background_position=&#8221;top_left&#8221; background_repeat=&#8221;repeat&#8221; hover_enabled=&#8221;0&#8243; column_structure=&#8221;3_5,2_5&#8243; sticky_enabled=&#8221;0&#8243;][et_pb_column type=&#8221;3_5&#8243; _builder_version=&#8221;3.25&#8243; custom_padding=&#8221;|||&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_text _builder_version=&#8221;4.9.4&#8243; background_size=&#8221;initial&#8221; background_position=&#8221;top_left&#8221; background_repeat=&#8221;repeat&#8221; hover_enabled=&#8221;0&#8243; module_class=&#8221;blog-left-content&#8221; sticky_enabled=&#8221;0&#8243;]<\/p>\n<p><!-- divi:paragraph --><strong>Introduction<\/strong><\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->Google celebrated Flutter day on June 25th, a day for encouraging developers to try out their UI toolkit. Flutter is one of the most widely used open source frameworks based on Dart for cross platform development using a single codebase. The highlight of this being that the applications are developed using native compiled and not a simple JS bridge or WebVIew as seen in many other cross platform development tools. When developing mobile applications, most commonly it is a major headache for most developers to render dynamic lists in mobile applications, as it may result in significant performance losses if not handled properly.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->Flutter already has provisions to manage large lists efficiently in memory by using the ListView widget. A straightforward approach would be to fetch all the data required to render the list and let the ListView handle the rest so that the resource utilization is kept in check. The lists still might be large when fetched from an external database and therefore result in large initial load times and excessive bandwidth utilization. So how should we tackle such an issue?<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code>ScrollController _scrollController = ScrollController();\nvoid initState() {\nsuper.initState();\nthis._fetchData();\n_scrollController.addListener(() {\nif (_scrollController.position.pixels ==\n_scrollController.position.maxScrollExtent) {\n_fetchData();\n}\n});\n}<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph --><strong>Enter Lazy Loading<\/strong><\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->Lazy loading has been around for almost all major web applications. Basically, we load a minimal set of data initially to an empty list and append the next set of data to the list when required. This can be easily implemented by identifying whether the user has reached the end of the list and then fetching the next set accordingly. To identify whether the user has scrolled to the end of the list, we can initialize a ScrollController _scrollController which can listen to the event when the user reaches the end of the list with comparing the current position _scrollController.position.pixels with that of maximum scroll extent\u00a0 _scrollController.position.maxScrollExtent.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->Do keep in mind that the ScrollController will work in the background even if the user navigates away from the page and hence there are possibilities of memory leak if they aren\u2019t cleared after use.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code>@override\nvoid dispose() {\n_scrollController.dispose();\nsuper.dispose();\n}<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph --><strong>Fetching data<\/strong><\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->To fetch data from an API the http package from dart can be used.\u00a0<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code>import 'package:http\/http.dart' as http;<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph -->Here we must add http as a dependency in the pubspec.yaml file as shown below.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code>dependencies:\nflutter:\nsdk: flutter\ncupertino_icons: ^0.1.3\nhttp: 0.12.1<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph -->The _fetchData() function will get the API response and push the results to the albums<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->List. Each time a successful fetch occurs, we set the state using the setState() function, which in turn re-renders the list to show the newly fetched items. The API used here is a placeholder made available by jsonplaceholder.typicode.com.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->The setState() function is actually the same as that of React\/React Native. It simply notifies the widget that there are some changes in the variable states and hence UI needs to be re-rendered.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code> String apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/1\/photos\";\n int pageNo = 1;\n ScrollController _scrollController = ScrollController();\n bool isLoading = false;\n List albums = List();\n \n void _fetchData() async {\n   if (!isLoading) {\n     setState(() {\n       isLoading = true;\n     });\n     try {\n       final response = await http.get(apiUrl);\n       if (response.statusCode == 200) {\n         List albumList = List();\n         var resultBody;\n         pageNo =\n             (pageNo &gt; 100) ? 1 : pageNo++; \/\/ resetting and incrementing page\n         apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/$pageNo\/photos\";\n \n         resultBody = jsonDecode(response.body);\n         for (int i = 0; i &lt; resultBody.length; i++) {\n           albumList.add(resultBody[i]);\n         }\n         setState(() {\n           isLoading = false;\n           albums.addAll(albumList);\n         });\n       } else {\n         setState(() {\n           isLoading = false;\n         });\n       }\n     } catch (_) {\n       setState(() {\n         isLoading = false;\n       });\n     }\n   }\n<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph --><strong>Using ListView.builder()<\/strong><\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->ListView.builder() is a named constructor which can be used to generate dynamic content. Instead of returning a static widget containing all the list items, the Listview.builder() function will create the ListView items as specified in the itemBuilder function and inflate them whenever they are required to be displayed on the screen. Thus the\u00a0 ListView.builder() effectively manages memory when the list gets large. Here the _buildProgressIndicator()is used to indicate that the app is making a network call.\u00a0<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code>Widget _buildList() {\n   return albums.length &lt; 1\n       ? Center(\n           child: Container(\n             child: Text(\n               'No data',\n               style: TextStyle(\n                 fontSize: 20.0,\n               ),\n             ),\n           ),\n         )\n       : ListView.builder(\n           itemCount: albums.length + 1,\n           itemBuilder: (BuildContext context, int index) {\n             if (index == albums.length) {\n               return _buildProgressIndicator();\n             } else {\n               return Padding(\n                 padding: EdgeInsets.all(8.0),\n                 child: Card(\n                   child: ListTile(\n                     leading: CircleAvatar(\n                       backgroundImage:\n                           NetworkImage(albums[index]['thumbnailUrl']),\n                     ),\n                     title: Text((albums[index]['title'])),\n                     onTap: () {\n                       print(albums[index]);\n                     },\n                   ),\n                 ),\n               );\n             }\n           },\n           controller: _scrollController,\n         );\n \n Widget _buildProgressIndicator() {\n   return Padding(\n     padding: const EdgeInsets.all(8.0),\n     child: Center(\n       child: Opacity(\n         opacity: isLoading ? 1.0 : 00,\n         child: CircularProgressIndicator(),\n       ),\n     ),\n   );\n }\n<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph -->The final <strong>main.dart <\/strong>should look like this:<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:code --><\/p>\n<pre class=\"wp-block-code\"><code>import 'package:flutter\/material.dart';\nimport 'package:http\/http.dart' as http;\nimport 'dart:convert';\n \nvoid main() =&gt; runApp(MyApp());\nclass MyApp extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n   return MaterialApp(\n     title: 'Flutter Demo',\n     theme: ThemeData(\n       primarySwatch: Colors.blue,\n     ),\n     home: MyHomePage(),\n   );\n }\n}\n \nclass MyHomePage extends StatefulWidget {\n @override\n _MyHomePageState createState() =&gt; _MyHomePageState();\n}\n \nclass _MyHomePageState extends State&lt;MyHomePage&gt; {\n String apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/1\/photos\";\n int pageNo = 1;\n ScrollController _scrollController = ScrollController();\n bool isLoading = false;\n List albums = List();\n void _fetchData() async {\n   if (!isLoading) {\n     setState(() { isLoading = true; });\n     final response = await http.get(apiUrl);\n     if (response.statusCode == 200) {\n       List albumList = List();\n       var resultBody;\n       pageNo = (pageNo &gt; 100) ? 1 : pageNo++; \/\/ resetting and incrementing page\n       apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/$pageNo\/photos\";\n       resultBody = jsonDecode(response.body);\n       for (int i = 0; i &lt; resultBody.length; i++) {\n         albumList.add(resultBody[i]);\n       }\n       setState(() {\n         isLoading = false;\n         albums.addAll(albumList);\n       });\n     } else {\n       setState(() { isLoading = false; });\n     }\n   }\n }\n \n @override\n void initState() {\n   this._fetchData();\n   super.initState();\n   _scrollController.addListener(() {\n     if (_scrollController.position.pixels ==\n         _scrollController.position.maxScrollExtent) {\n       _fetchData();\n     }\n   });\n }\n \n @override\n void dispose() {\n   _scrollController.dispose();\n   super.dispose();\n }\n \n Widget _buildProgressIndicator() {\n   return Padding(\n     padding: const EdgeInsets.all(8.0),\n     child: Center(\n       child: Opacity(\n         opacity: isLoading ? 1.0 : 00,\n         child: CircularProgressIndicator(),\n       ),\n     ),\n   );\n }\n \n Widget _buildList() {\n   return albums.length &lt; 1\n       ? Center(\n           child: Container(\n             child: Text(\n               'No data',\n               style: TextStyle(\n                 fontSize: 20.0,\n               ),\n             ),\n           ),\n         )\n       : ListView.builder(\n           itemCount: albums.length + 1,\n           itemBuilder: (BuildContext context, int index) {\n             if (index == albums.length) {\n               return _buildProgressIndicator();\n             } else {\n               return Padding(\n                 padding: EdgeInsets.all(8.0),\n                 child: Card(\n                   child: ListTile(\n                     leading: CircleAvatar(\n                       backgroundImage:\n                           NetworkImage(albums[index]['thumbnailUrl']),\n                     ),\n                     title: Text((albums[index]['title'])),\n                     onTap: () {\n                       print(albums[index]);\n                     },\n                   ),\n                 ),\n               );\n             }\n           },\n           controller: _scrollController,\n         );\n }\n \n @override\n Widget build(BuildContext context) {\n   return Scaffold(\n     appBar: AppBar(\n       title: const Text(\"Pagination\"),\n     ),\n     body: Container(\n       child: _buildList(),\n     ),\n     resizeToAvoidBottomPadding: false,\n   );\n }\n}\n \n \n<\/code><\/pre>\n<p><!-- \/divi:code --><\/p>\n<p><!-- divi:paragraph -->The output for a list with progress widget will be as shown below.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:image {\"align\":\"center\"} --><\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/4GVr4zMTZnZEqDqnWr4IKzbFriSMYoLDxglUdmiFEgJtyh-AakPzt1JorRRjKRPxInLdsTcn39vnogiKPmchojfwJPmKjF4gBTGdja8wTE8l5lzAvi5xG-cWSbGsrEyB7JK4mf5vcckjvdsvXg\" alt=\"\" \/><\/figure>\n<\/div>\n<p><!-- \/divi:image --><\/p>\n<p><!-- divi:paragraph --><strong>Conclusion<\/strong><\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->Flutter is an incredible framework with lots of options for customization. Understanding Flutter widgets properly will allow you to quickly prototype a beautiful user interface which greatly reduces time for development and allows developers to focus on more important aspects of the application. Most importantly, seasoned developers can adopt various approaches for managing complex states in a way that they would prefer rather than following a set standard.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->In the application built, it can be observed that most of the complexity involved in building a paginated list is easier to handle in Flutter. The implementation shown is a general one and can be modified to use for other use cases or real time databases like Firebase.\u00a0<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->At Confianz Global, we have built some of the best\u00a0<a href=\"https:\/\/www.confianzit.com\/odoo-apps\">Odoo apps<\/a>\u00a0for 3rd party Integration. Feel free to contact us at any time with questions or concerns about your\u00a0<a href=\"https:\/\/www.confianzit.com\/odoo-apps\">Odoo Apps<\/a>. \u00a0We\u00a0also provide\u00a0best\u00a0<a href=\"https:\/\/www.confianzit.com\/openerp-customization\">Odoo\u00a0ERP Customization<\/a>,\u00a0<a href=\"https:\/\/www.confianzit.com\/odoo-implementation\">Implementation<\/a>\u00a0&amp;\u00a0<a href=\"https:\/\/www.confianzit.com\/odoo-support-maintenance\">technical support services<\/a>.<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p><!-- divi:paragraph -->So why wait?\u00a0<a href=\"https:\/\/www.confianzit.com\/contact-us\">Call us today<\/a>\u00a0to get started!<\/p>\n<p><!-- \/divi:paragraph --><\/p>\n<p>[\/et_pb_text][\/et_pb_column][et_pb_column type=&#8221;2_5&#8243; _builder_version=&#8221;3.25&#8243; custom_padding=&#8221;|||&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_code _builder_version=&#8221;4.9.4&#8243; _module_preset=&#8221;default&#8221; locked=&#8221;off&#8221; global_module=&#8221;2151&#8243;]<\/p>\n<div class=\"blog-floating-form\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_62 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-1'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.confianzit.com\/cit-blog\/flutter-paginated-lazy-loading-listview\/#Talk_to_our_experts_now\" title=\"    Talk to our experts now  \">    Talk to our experts now  <\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.confianzit.com\/cit-blog\/flutter-paginated-lazy-loading-listview\/#Talk_To_Our_Experts_Now\" title=\"Talk To Our Experts Now\n\t\">Talk To Our Experts Now\n\t<\/a><\/li><\/ul><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h1><span class=\"ez-toc-section\" id=\"Talk_to_our_experts_now\"><\/span><!-- [et_pb_line_break_holder] -->    Talk to our experts now<!-- [et_pb_line_break_holder] -->  <span class=\"ez-toc-section-end\"><\/span><\/h1>\n<p><!-- [et_pb_line_break_holder] -->  \n<div class=\"wpcf7 no-js\" id=\"wpcf7-f1888-o1\" lang=\"en-US\" dir=\"ltr\">\n<div class=\"screen-reader-response\"><p role=\"status\" aria-live=\"polite\" aria-atomic=\"true\"><\/p> <ul><\/ul><\/div>\n<form action=\"\/cit-blog\/wp-json\/wp\/v2\/posts\/1010#wpcf7-f1888-o1\" method=\"post\" class=\"wpcf7-form init\" aria-label=\"Contact form\" novalidate=\"novalidate\" data-status=\"init\">\n<div style=\"display: none;\">\n<input type=\"hidden\" name=\"_wpcf7\" value=\"1888\" \/>\n<input type=\"hidden\" name=\"_wpcf7_version\" value=\"5.8.6\" \/>\n<input type=\"hidden\" name=\"_wpcf7_locale\" value=\"en_US\" \/>\n<input type=\"hidden\" name=\"_wpcf7_unit_tag\" value=\"wpcf7-f1888-o1\" \/>\n<input type=\"hidden\" name=\"_wpcf7_container_post\" value=\"0\" \/>\n<input type=\"hidden\" name=\"_wpcf7_posted_data_hash\" value=\"\" \/>\n<input type=\"hidden\" name=\"_wpcf7_recaptcha_response\" value=\"\" \/>\n<\/div>\n<div class=\"form-block\" style=\"    background: #fff;\">\n\t<h3 style=\"    background: #0C2464;\n    border-bottom: 5px solid #cecece;\n    border-radius: 5px 5px 90px 90px;\n    margin: 0 auto;\n    text-align: center;\n    padding: 20px;\n    color: #fff;    margin-bottom: 15px;\"><span class=\"ez-toc-section\" id=\"Talk_To_Our_Experts_Now\"><\/span><b>Talk To Our Experts Now<\/b>\n\t<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\t<div style=\"padding:20px;\">\n\t\t<p><span class=\"wpcf7-form-control-wrap\" data-name=\"your-name\"><input size=\"40\" class=\"wpcf7-form-control wpcf7-text wpcf7-validates-as-required your-name\" aria-required=\"true\" aria-invalid=\"false\" placeholder=\"Name\" value=\"\" type=\"text\" name=\"your-name\" \/><\/span>\n\t\t<\/p>\n\t\t<p><span class=\"wpcf7-form-control-wrap\" data-name=\"your-email\"><input size=\"40\" class=\"wpcf7-form-control wpcf7-email wpcf7-validates-as-required wpcf7-text wpcf7-validates-as-email your-email\" aria-required=\"true\" aria-invalid=\"false\" placeholder=\"Email\" value=\"\" type=\"email\" name=\"your-email\" \/><\/span>\n\t\t<\/p>\n\t\t<p><span class=\"wpcf7-form-control-wrap\" data-name=\"your-number\"><input size=\"40\" class=\"wpcf7-form-control wpcf7-tel wpcf7-validates-as-required wpcf7-text wpcf7-validates-as-tel your-number\" aria-required=\"true\" aria-invalid=\"false\" placeholder=\"Phone Number\" value=\"\" type=\"tel\" name=\"your-number\" \/><\/span>\n\t\t<\/p>\n\t\t<p><span class=\"wpcf7-form-control-wrap\" data-name=\"message\"><textarea cols=\"40\" rows=\"10\" class=\"wpcf7-form-control wpcf7-textarea wpcf7-validates-as-required form-message\" aria-required=\"true\" aria-invalid=\"false\" placeholder=\"Message\" name=\"message\"><\/textarea><\/span>\n\t\t<\/p>\n\t<span class=\"wpcf7-form-control-wrap recaptcha\" data-name=\"recaptcha\"><span data-sitekey=\"6LfFkQATAAAAAIYlZ_UH9UozO-OLkpAaWPWx6QtM\" class=\"wpcf7-form-control wpcf7-recaptcha g-recaptcha\"><\/span>\r\n<noscript>\r\n\t<div class=\"grecaptcha-noscript\">\r\n\t\t<iframe loading=\"lazy\" src=\"https:\/\/www.google.com\/recaptcha\/api\/fallback?k=6LfFkQATAAAAAIYlZ_UH9UozO-OLkpAaWPWx6QtM\" frameborder=\"0\" scrolling=\"no\" width=\"310\" height=\"430\">\r\n\t\t<\/iframe>\r\n\t\t<textarea name=\"g-recaptcha-response\" rows=\"3\" cols=\"40\" placeholder=\"reCaptcha Response Here\">\r\n\t\t<\/textarea>\r\n\t<\/div>\r\n<\/noscript>\r\n<\/span>\n\t\t<div class=\"form-buttons\">\n\t\t\t<p><input class=\"wpcf7-form-control wpcf7-submit has-spinner\" type=\"submit\" value=\"Get a free quote\" \/>\n\t\t\t<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div><div class=\"wpcf7-response-output\" aria-hidden=\"true\"><\/div>\n<\/form>\n<\/div>\n<!-- [et_pb_line_break_holder] --><\/div>\n<p>[\/et_pb_code][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Google celebrated Flutter day on June 25th, a day for encouraging developers to try out their UI toolkit. Flutter is one of the most widely used open source frameworks based on Dart for cross platform development using a single codebase. The highlight of this being that the applications are developed using native compiled and [&hellip;]<\/p>\n","protected":false},"author":11,"featured_media":834,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"on","_et_pb_old_content":"<!-- wp:paragraph -->\n<p><strong>Introduction<\/strong><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Google celebrated Flutter day on June 25th, a day for encouraging developers to try out their UI toolkit. Flutter is one of the most widely used open source frameworks based on Dart for cross platform development using a single codebase. The highlight of this being that the applications are developed using native compiled and not a simple JS bridge or WebVIew as seen in many other cross platform development tools. When developing mobile applications, most commonly it is a major headache for most developers to render dynamic lists in mobile applications, as it may result in significant performance losses if not handled properly.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Flutter already has provisions to manage large lists efficiently in memory by using the ListView widget. A straightforward approach would be to fetch all the data required to render the list and let the ListView handle the rest so that the resource utilization is kept in check. The lists still might be large when fetched from an external database and therefore result in large initial load times and excessive bandwidth utilization. So how should we tackle such an issue?<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>ScrollController _scrollController = ScrollController();\nvoid initState() {\nsuper.initState();\nthis._fetchData();\n_scrollController.addListener(() {\nif (_scrollController.position.pixels ==\n_scrollController.position.maxScrollExtent) {\n_fetchData();\n}\n});\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p><strong>Enter Lazy Loading<\/strong><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Lazy loading has been around for almost all major web applications. Basically, we load a minimal set of data initially to an empty list and append the next set of data to the list when required. This can be easily implemented by identifying whether the user has reached the end of the list and then fetching the next set accordingly. To identify whether the user has scrolled to the end of the list, we can initialize a ScrollController _scrollController which can listen to the event when the user reaches the end of the list with comparing the current position _scrollController.position.pixels with that of maximum scroll extent&nbsp; _scrollController.position.maxScrollExtent.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Do keep in mind that the ScrollController will work in the background even if the user navigates away from the page and hence there are possibilities of memory leak if they aren\u2019t cleared after use.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>@override\nvoid dispose() {\n_scrollController.dispose();\nsuper.dispose();\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p><strong>Fetching data<\/strong><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To fetch data from an API the http package from dart can be used.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>import 'package:http\/http.dart' as http;<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Here we must add http as a dependency in the pubspec.yaml file as shown below.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>dependencies:\nflutter:\nsdk: flutter\ncupertino_icons: ^0.1.3\nhttp: 0.12.1<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The _fetchData() function will get the API response and push the results to the albums<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>List. Each time a successful fetch occurs, we set the state using the setState() function, which in turn re-renders the list to show the newly fetched items. The API used here is a placeholder made available by jsonplaceholder.typicode.com.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The setState() function is actually the same as that of React\/React Native. It simply notifies the widget that there are some changes in the variable states and hence UI needs to be re-rendered.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code> String apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/1\/photos\";\n int pageNo = 1;\n ScrollController _scrollController = ScrollController();\n bool isLoading = false;\n List albums = List();\n \n void _fetchData() async {\n   if (!isLoading) {\n     setState(() {\n       isLoading = true;\n     });\n     try {\n       final response = await http.get(apiUrl);\n       if (response.statusCode == 200) {\n         List albumList = List();\n         var resultBody;\n         pageNo =\n             (pageNo &gt; 100) ? 1 : pageNo++; \/\/ resetting and incrementing page\n         apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/$pageNo\/photos\";\n \n         resultBody = jsonDecode(response.body);\n         for (int i = 0; i &lt; resultBody.length; i++) {\n           albumList.add(resultBody&#91;i]);\n         }\n         setState(() {\n           isLoading = false;\n           albums.addAll(albumList);\n         });\n       } else {\n         setState(() {\n           isLoading = false;\n         });\n       }\n     } catch (_) {\n       setState(() {\n         isLoading = false;\n       });\n     }\n   }\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p><strong>Using ListView.builder()<\/strong><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>ListView.builder() is a named constructor which can be used to generate dynamic content. Instead of returning a static widget containing all the list items, the Listview.builder() function will create the ListView items as specified in the itemBuilder function and inflate them whenever they are required to be displayed on the screen. Thus the&nbsp; ListView.builder() effectively manages memory when the list gets large. Here the _buildProgressIndicator()is used to indicate that the app is making a network call.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>Widget _buildList() {\n   return albums.length &lt; 1\n       ? Center(\n           child: Container(\n             child: Text(\n               'No data',\n               style: TextStyle(\n                 fontSize: 20.0,\n               ),\n             ),\n           ),\n         )\n       : ListView.builder(\n           itemCount: albums.length + 1,\n           itemBuilder: (BuildContext context, int index) {\n             if (index == albums.length) {\n               return _buildProgressIndicator();\n             } else {\n               return Padding(\n                 padding: EdgeInsets.all(8.0),\n                 child: Card(\n                   child: ListTile(\n                     leading: CircleAvatar(\n                       backgroundImage:\n                           NetworkImage(albums&#91;index]&#91;'thumbnailUrl']),\n                     ),\n                     title: Text((albums&#91;index]&#91;'title'])),\n                     onTap: () {\n                       print(albums&#91;index]);\n                     },\n                   ),\n                 ),\n               );\n             }\n           },\n           controller: _scrollController,\n         );\n \n Widget _buildProgressIndicator() {\n   return Padding(\n     padding: const EdgeInsets.all(8.0),\n     child: Center(\n       child: Opacity(\n         opacity: isLoading ? 1.0 : 00,\n         child: CircularProgressIndicator(),\n       ),\n     ),\n   );\n }\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The final <strong>main.dart <\/strong>should look like this:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>import 'package:flutter\/material.dart';\nimport 'package:http\/http.dart' as http;\nimport 'dart:convert';\n \nvoid main() =&gt; runApp(MyApp());\nclass MyApp extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n   return MaterialApp(\n     title: 'Flutter Demo',\n     theme: ThemeData(\n       primarySwatch: Colors.blue,\n     ),\n     home: MyHomePage(),\n   );\n }\n}\n \nclass MyHomePage extends StatefulWidget {\n @override\n _MyHomePageState createState() =&gt; _MyHomePageState();\n}\n \nclass _MyHomePageState extends State&lt;MyHomePage&gt; {\n String apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/1\/photos\";\n int pageNo = 1;\n ScrollController _scrollController = ScrollController();\n bool isLoading = false;\n List albums = List();\n void _fetchData() async {\n   if (!isLoading) {\n     setState(() { isLoading = true; });\n     final response = await http.get(apiUrl);\n     if (response.statusCode == 200) {\n       List albumList = List();\n       var resultBody;\n       pageNo = (pageNo &gt; 100) ? 1 : pageNo++; \/\/ resetting and incrementing page\n       apiUrl = \"https:\/\/jsonplaceholder.typicode.com\/albums\/$pageNo\/photos\";\n       resultBody = jsonDecode(response.body);\n       for (int i = 0; i &lt; resultBody.length; i++) {\n         albumList.add(resultBody&#91;i]);\n       }\n       setState(() {\n         isLoading = false;\n         albums.addAll(albumList);\n       });\n     } else {\n       setState(() { isLoading = false; });\n     }\n   }\n }\n \n @override\n void initState() {\n   this._fetchData();\n   super.initState();\n   _scrollController.addListener(() {\n     if (_scrollController.position.pixels ==\n         _scrollController.position.maxScrollExtent) {\n       _fetchData();\n     }\n   });\n }\n \n @override\n void dispose() {\n   _scrollController.dispose();\n   super.dispose();\n }\n \n Widget _buildProgressIndicator() {\n   return Padding(\n     padding: const EdgeInsets.all(8.0),\n     child: Center(\n       child: Opacity(\n         opacity: isLoading ? 1.0 : 00,\n         child: CircularProgressIndicator(),\n       ),\n     ),\n   );\n }\n \n Widget _buildList() {\n   return albums.length &lt; 1\n       ? Center(\n           child: Container(\n             child: Text(\n               'No data',\n               style: TextStyle(\n                 fontSize: 20.0,\n               ),\n             ),\n           ),\n         )\n       : ListView.builder(\n           itemCount: albums.length + 1,\n           itemBuilder: (BuildContext context, int index) {\n             if (index == albums.length) {\n               return _buildProgressIndicator();\n             } else {\n               return Padding(\n                 padding: EdgeInsets.all(8.0),\n                 child: Card(\n                   child: ListTile(\n                     leading: CircleAvatar(\n                       backgroundImage:\n                           NetworkImage(albums&#91;index]&#91;'thumbnailUrl']),\n                     ),\n                     title: Text((albums&#91;index]&#91;'title'])),\n                     onTap: () {\n                       print(albums&#91;index]);\n                     },\n                   ),\n                 ),\n               );\n             }\n           },\n           controller: _scrollController,\n         );\n }\n \n @override\n Widget build(BuildContext context) {\n   return Scaffold(\n     appBar: AppBar(\n       title: const Text(\"Pagination\"),\n     ),\n     body: Container(\n       child: _buildList(),\n     ),\n     resizeToAvoidBottomPadding: false,\n   );\n }\n}\n \n \n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>The output for a list with progress widget will be as shown below.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"align\":\"center\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img src=\"https:\/\/lh4.googleusercontent.com\/4GVr4zMTZnZEqDqnWr4IKzbFriSMYoLDxglUdmiFEgJtyh-AakPzt1JorRRjKRPxInLdsTcn39vnogiKPmchojfwJPmKjF4gBTGdja8wTE8l5lzAvi5xG-cWSbGsrEyB7JK4mf5vcckjvdsvXg\" alt=\"\"\/><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p><strong>Conclusion<\/strong><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Flutter is an incredible framework with lots of options for customization. Understanding Flutter widgets properly will allow you to quickly prototype a beautiful user interface which greatly reduces time for development and allows developers to focus on more important aspects of the application. Most importantly, seasoned developers can adopt various approaches for managing complex states in a way that they would prefer rather than following a set standard.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>In the application built, it can be observed that most of the complexity involved in building a paginated list is easier to handle in Flutter. The implementation shown is a general one and can be modified to use for other use cases or real time databases like Firebase.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>At Confianz Global, we have built some of the best&nbsp;<a href=\"https:\/\/www.confianzit.com\/odoo-apps\">Odoo apps<\/a>&nbsp;for 3rd party Integration. Feel free to contact us at any time with questions or concerns about your&nbsp;<a href=\"https:\/\/www.confianzit.com\/odoo-apps\">Odoo Apps<\/a>. &nbsp;We&nbsp;also provide&nbsp;best&nbsp;<a href=\"https:\/\/www.confianzit.com\/openerp-customization\">Odoo&nbsp;ERP Customization<\/a>,&nbsp;<a href=\"https:\/\/www.confianzit.com\/odoo-implementation\">Implementation<\/a>&nbsp;&amp;&nbsp;<a href=\"https:\/\/www.confianzit.com\/odoo-support-maintenance\">technical support services<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>So why wait?&nbsp;<a href=\"https:\/\/www.confianzit.com\/contact-us\">Call us today<\/a>&nbsp;to get started!<\/p>\n<!-- \/wp:paragraph -->","_et_gb_content_width":"","footnotes":""},"categories":[7],"tags":[161,162,163,165,164],"_links":{"self":[{"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/posts\/1010"}],"collection":[{"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/comments?post=1010"}],"version-history":[{"count":7,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/posts\/1010\/revisions"}],"predecessor-version":[{"id":27265,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/posts\/1010\/revisions\/27265"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/media\/834"}],"wp:attachment":[{"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/media?parent=1010"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/categories?post=1010"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.confianzit.com\/cit-blog\/wp-json\/wp\/v2\/tags?post=1010"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}