<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Rara Labs on Medium]]></title>
        <description><![CDATA[Stories by Rara Labs on Medium]]></description>
        <link>https://medium.com/@raralabs?source=rss-2c1ad3865b8e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*n_OJciaiKiF_OeM0MIHcdg.jpeg</url>
            <title>Stories by Rara Labs on Medium</title>
            <link>https://medium.com/@raralabs?source=rss-2c1ad3865b8e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 08:31:27 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@raralabs/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[5 Common Accounting Problems You Can Solve with SaaS Tools]]></title>
            <link>https://medium.com/@raralabs/5-common-accounting-problems-you-can-solve-with-saas-tools-a51a0b595649?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/a51a0b595649</guid>
            <category><![CDATA[saas-tools]]></category>
            <category><![CDATA[accounting]]></category>
            <category><![CDATA[saas]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Tue, 02 Jan 2024 11:24:25 GMT</pubDate>
            <atom:updated>2024-01-03T04:14:37.423Z</atom:updated>
            <content:encoded><![CDATA[<p>Running a business without a good accountant is like sailing a ship without a compass. Whether it’s a shop, retail establishment, or a bustling restaurant, only a good accountant can ensure the business stays organized and out of money troubles.</p><p>A business owner instantly knows the financial state and the business’ needs at a quick glance if the books are well-maintained. Without one, well… the business is adrift, and unable to succeed long-term, even when making recurrent small profits.</p><p>With booming businesses entering the digital realm, the role of accounting software has become more and more apparent.</p><p>Amid the increasing adoption of Software as a Service (SaaS) for accounting purposes, Tigg stands out as a crucial tool in the Nepali business landscape for financial management.</p><p><a href="https://www.tiggapp.com/">Tigg</a> app is an easy-to-use accounting software specially designed for restros and wholesale/retail businesses after an in-depth research about the most common pitfalls that SMEs in Nepal face when getting off the ground. With Tigg, you can create tasks on the fly from anywhere, easily manage the data of your customers and products, and seamlessly integrate it with your current system.</p><p>From real-time reporting to customizable branding, <a href="https://www.tiggapp.com/">Tigg’s</a> tailored approach to the nuances of businesses remarkably solves the most frequently-made issues in accounting. In this blog, let’s explore 5 most commonly encountered problems for SMEs in accounting and how Tigg solves it to ensure a smoother voyage toward financial success for its businesses:</p><h3>1. Document Records</h3><p>Misplacing documents such as invoices, bills, and receipts can lead to incomplete or inaccurate financial records, which in turn can result in errors in financial reporting and decision-making. This apparently is a common hiccup for businesses, especially during an audit or tax enquiry.</p><p>Tigg solves this issue with its robust Document Management feature. With Tigg, you can simply snap pictures of your invoices and bills, and Tigg organizes them neatly in one place. This not only makes record-keeping easy but also ensures quick access to essential documents when needed.</p><h3>2. User Access Headaches</h3><p>When businesses use accounting software, they often have multiple team members who need varying levels of access to the system. Inadequate user access controls can compromise the confidentiality of sensitive information, leading to violations and breaches.</p><p>Similarly, highly restricted user access can hinder collaboration and workflow efficiency. Managing user permissions can be a headache, especially as your team grows.</p><p>Tigg simplifies this with its User Management feature. You can create multiple users with specific permissions, enhancing security and streamlining collaboration by focusing individuals on their designated tasks, all the while providing a tamper-proof activity log for every action taken. User comments and notes can be seamlessly incorporated, enhancing communication and collaboration. And all of this is easily accessible on your dashboard.</p><h3>3. Real-Time Reporting and Insights</h3><p>Waiting for accounting and tax reports can hinder a business’s ability to make timely and informed decisions.</p><p>TiggApp addresses this challenge by providing up-to-the-minute insights, allowing businesses to stay on top of their finances and make adjustments promptly. Tigg provides Real-Time Reports, offering up-to-the-minute insights. This empowers businesses to make timely adjustments and strategic decisions based on accurate and current financial data.</p><h3>4. Cloud-Based Accessibility from Anywhere</h3><p>Traditional accounting software ties you down to a specific location, hindering remote work. Tigg breaks these chains with its Cloud-Based Solution. You can access your financial data from anywhere with an internet connection while enjoying automatic updates, real-time collaboration, and robust security measures, including data encryption and regular backups.</p><p>Tigg is a versatile tool that goes beyond traditional accounting software, addressing common mistakes with great features. By streamlining document management, offering customization options, simplifying user management, providing real-time reports, and embracing a cloud-based approach, Tigg becomes a valuable asset for SMEs in Nepal.</p><h3>5. Customization and Branding</h3><p>Customising reports and aligning them with a brand image is crucial for a company to present a professional and cohesive image to clients and stakeholders. It allows businesses to maintain consistency in their communication and reinforces their brand identity. Maintaining a consistent brand image is crucial for any business.</p><p>Tigg addresses the problem of generic quotes and invoices with extensive customization options, all the while ensuring that your financial information is complete and accurate. You can tailor your quotes and invoices to match your company branding, creating a professional and cohesive image for your clients.</p><h3>Conclusion</h3><p>Beyond core accounting, what truly separates Tigg from other software is how it manages all documents, tasks, and contacts. Tigg can manage accounting, restaurants, wholesale, and retail shops using a single cloud-based software.</p><p>Developed with non-accountants in mind, Tigg is user-friendly and can be set up within a few hours to a day, depending on your data size. Get the most efficient way to manage accounting &amp; finance for modern businesses with Tigg.</p><p>To learn more about Tigg and its services, visit <a href="https://www.tiggapp.com/">https://www.tiggapp.com/</a> or shoot us a mail at:</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a51a0b595649" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SaaS vs. On-Premise Accounting Software: Let’s Break It Down]]></title>
            <link>https://medium.com/@raralabs/saas-vs-on-premise-accounting-software-lets-break-it-down-08de1d1d3518?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/08de1d1d3518</guid>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Mon, 11 Dec 2023 09:46:39 GMT</pubDate>
            <atom:updated>2023-12-11T09:46:39.088Z</atom:updated>
            <content:encoded><![CDATA[<p>Not all businesses need accounting software, but it’s undeniable how handy it can end up being. A well-suited accounting software can efficiently manage finances, track income and expenses, ensure tax compliance, and provide valuable insights into your business performance.</p><p>There are different approaches you can do this. How you keep track of your finances mainly depends on the size of your company and its financial complexity. Self-employed individuals or freelancers might opt for manual bookkeeping methods, using a notebook and pen. Small businesses might smell a need for organizing everything into spreadsheets so that it helps with saving time and future reference.</p><p>But as your business expands and financial intricacies grow, relying solely on these methods may result in a tangled web of finances. While automating certain processes may initially seem efficient, it could lead to a spaghetti of accounts, requiring frequent manual intervention and rendering the software ineffective. This, in turn, may incur additional costs through hired personnel for data entries, lost time, diminished client trust, and a less reliable financial tracking system overall.</p><p>At this point, you might opt for a one-time cost on an on-premise software like Tally, or paying a recurring subscription fee for a SaaS (Software as a Service). In this blog, we’ll dive deeper into the pros and cons of using both traditional software and SaaS for your business, and understand total costs of ownership for what suits your business the best, so that you don’t have to.</p><h3>1. Cost</h3><p>Traditional software usually requires a one-time purchase, which can be a significant upfront cost, but there are no recurring fees unless you opt for updates or support. On the other hand, SaaS operates on a subscription model, meaning you pay a recurring fee, often monthly or annually. This can be more manageable for businesses with tight cash flow. SaaS services are scalable, meaning you can increase or decrease your service usage, but you’ll always end up paying only for what you use.</p><h3>2. Installation Methods</h3><p>Traditional software needs to be installed on each device that will use it, which can be time-consuming and require technical knowledge. SaaS, however, is cloud-based and accessible via the internet, eliminating the need for installation and making it easier to get up and running.</p><h3>3. Accessibility and Device Dependency</h3><p>With traditional software, you’re limited to the device it’s installed on, which can restrict when and where you can access your financial data. SaaS offers more flexibility on this, with the ability to access your data from any device as long as you have a stable internet connection, making it a good option for businesses with remote workers or multiple locations.</p><h3>4. General vs Tailored</h3><p>Traditional software may offer a more comprehensive feature set, but it may also include many features you don’t need, making it more complex to use. SaaS providers often offer a range of plans with different features, allowing you to choose the one that best fits your business needs and budget.</p><h3>5. Security</h3><p>Both traditional software and SaaS have their own security considerations. With traditional software, your data is stored on your own servers or devices, so you have full control over its security. However, this also means you’re responsible for maintaining that security. SaaS providers store data on the cloud and handle security on their end. Often, the resources employed by SaaS providers use robust security measures, but you’re entrusting your sensitive financial data to a third party.</p><h3>6. Server Setup</h3><p>Traditional software requires you to have your own server setup, which can be costly and require ongoing maintenance. SaaS is hosted on the provider’s servers, reducing the need for you to invest in and maintain your own server infrastructure.</p><h3>7. Compatibility</h3><p>Traditional software may not always play well with other systems you’re using, which can lead to inefficiencies and frustration. SaaS is typically designed to integrate easily with other popular software, helping to streamline your workflows.</p><p>The choice between traditional software and SaaS depends on your specific business needs, budget, and resources. There are multiple accounting software providers but it is difficult to carefully consider what suits your business the best.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/717/0*n3VBRHHxrBSHhZy2" /></figure><p><em>Incurred Cost of Ownership over x years for both softwares</em></p><p>That is why we have Tigg. Tigg is the ultimate, all in one business management software, aimed at small and medium businesses (MSMEs) to manage all your business needs such as Accounting, CRM and sales in a single place.</p><p>Our cloud-based accounting system is specially designed to target the needs of Nepali SMEs to manage their finances &amp; taxes so that we can give you real-time business intelligence you need to chase growth.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=08de1d1d3518" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SaaS Data Privacy: In a Nutshell]]></title>
            <link>https://medium.com/@raralabs/saas-data-privacy-in-a-nutshell-d874dbfd616d?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d874dbfd616d</guid>
            <category><![CDATA[saas]]></category>
            <category><![CDATA[privacy-protection]]></category>
            <category><![CDATA[data-privacy]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Thu, 19 Oct 2023 05:44:16 GMT</pubDate>
            <atom:updated>2023-10-19T05:45:40.068Z</atom:updated>
            <content:encoded><![CDATA[<p>Before the era of SaaS arrived, enterprises purchased canned, off-the-shelf software for their needs.</p><p>With SaaS’s cloud-based model today, we not only enjoy easily elastic and accessible resources, but don’t have to worry about the huge upfront and maintenance costs either.</p><p>But in exchange for relieving these critical pain points, the trade-off has been security and our data privacy.</p><p>SaaS companies deal with fabulously large amounts of customer data and that’s why they end up spending loads to safeguard our data within secure walls.</p><p>But it’s just as fabulous to see a whole lot of big, fat companies who are practically tripping over themselves to sell this precious data for a quick buck.</p><h3>Efforts for SaaS Privacy in Nepal</h3><p>To prevent this, you might’ve heard of privacy guidelines GDPR, HIPAA, CCPA, and regulatory bodies being established who can take matters in their hands while our data rests in the hands of these companies.</p><p>Nepal too has made several efforts with the evolving need to safeguard our privacy. <a href="https://www.moljpa.gov.np/wp-content/uploads/2017/11/Constitution-of-Nepal-_English_-with-1st-Amendment_2.pdf">Article 29 of our Constitution</a> declares the right to privacy as a “fundamental right.”</p><p>And we’ve seen several iterations such as <a href="https://www.lawcommission.gov.np/en/wp-content/uploads/2019/07/The-Privacy-Act-2075-2018.pdf">the Individual Privacy Act, 2075</a>, and <a href="https://www.lawcommission.gov.np/np/wp-content/uploads/2020/12/%E0%A4%B5%E0%A5%88%E0%A4%AF%E0%A4%95%E0%A5%8D%E0%A4%A4%E0%A4%BF%E0%A4%95-%E0%A4%97%E0%A5%8B%E0%A4%AA%E0%A4%A8%E0%A5%80%E0%A4%AF%E0%A4%A4%E0%A4%BE-%E0%A4%B8%E0%A4%AE%E0%A5%8D%E0%A4%AC%E0%A4%A8%E0%A5%8D%E0%A4%A7%E0%A5%80-%E0%A4%A8%E0%A4%BF%E0%A4%AF%E0%A4%AE%E0%A4%BE%E0%A4%B5%E0%A4%B2%E0%A5%80-%E0%A5%A8%E0%A5%A6%E0%A5%AD%E0%A5%AD.pdf">the Individual Privacy Regulation, 2077</a> being enacted over the years. According to these, violating anybody’s privacy is a <em>criminal offence</em> whereby the victim can call for a criminal proceeding if their data is found to be collected or disclosed without their consent.</p><p>For such heinous criminal offences, the enormous price to pay has been a fine of Rs 30,000 fine, 3 years of prison, or both according to The Privacy Act.</p><p>As SaaS providers, it remains our topmost priority to safeguard our customer data with all means possible.</p><h3>Maintain Data Privacy for Competitive Advantage in SaaS</h3><h4><strong>1. Data and Data Security Compliance</strong></h4><p>Being compliant effectively builds unfair advantages for companies in the industry. Not only does this reduce the cybersec risks to the company infrastructure, but it also boosts customer loyalty and trust, and therefore, increases the company’s ROI.</p><p>Although they sound similar, data security compliance and data compliance are two different things.</p><p>Data security compliance primarily deals with how data is protected against breaches, loss, and unauthorized access.</p><p>Data compliance, however, covers much more than that. It includes how a company stores, transmits and uses information for their business operations.</p><p>Companies invest in both compliances to protect their customers’ data and to maintain industry standards like GDPR and HIPAA. Compliance violations usually end up in costly penalties, fines, and long-term repercussions on a business&#39;s reputation.</p><p>Cost of Damage due to Compliance Violation</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/730/0*Q4rVRT0gWWFEGOan" /></figure><h4>2. <strong>PII and Data Masking</strong></h4><p>PII, or Personally Identifiable Information, includes everything from names and addresses to email addresses, social security numbers, phone numbers, credit card numbers, and employee identification numbers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*2NtJiRbBAvApLHi5" /></figure><p>Anything that can identify you or access your personal information is PII.</p><p>Data masking methods include encryption (securing data with keys), substitution (replacing with similar data), shuffling (random replacement within columns), scrambling (rearranging numerical data), and nulling (replacing with null values for select users) to protect sensitive information while maintaining data utility.</p><p>With dynamic data masking, the stored data is masked in real-time, on-the-fly without exposing any personal data thus, ensuring the integrity of the user’s personal info.</p><h3>Final Thoughts</h3><p>Adding to these, if a company’s security architecture is compromised, it becomes highly vulnerable to cyber threats.</p><p>By integrating these data privacy principles into their business and IT strategies, companies can obtain a competitive advantage and respect customers’ preferences while improving their brand image, customer trust, and market position.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d874dbfd616d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Leveraging the power of object-oriented programming to build UIs with Flutter]]></title>
            <link>https://medium.com/@raralabs/leveraging-the-power-of-object-oriented-programming-to-build-uis-with-flutter-86230aa5b30?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/86230aa5b30</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[flutter-ui]]></category>
            <category><![CDATA[flutter-widget]]></category>
            <category><![CDATA[object-oriented-design]]></category>
            <category><![CDATA[object-oriented]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Wed, 06 Sep 2023 08:19:19 GMT</pubDate>
            <atom:updated>2023-09-07T07:53:14.366Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/88/0*oZRYw7aGYhbkd2sV.jpeg" /></figure><blockquote>Article Assumes the Reader knows basics of Flutter.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*kW9Z-TdRH1VMbOrY.png" /></figure><h3>The Problem</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*XCR9lVZvJeOFZTOm.png" /></figure><p>Your Designer gives you these buttons and now you gotta implement them in Flutter.</p><p>At first glance, you see that it’s 2 different types of buttons :</p><ol><li>Button with Text</li><li>Button with Icon and Text</li></ol><h3>How not to code these components?</h3><p>There are clearly 2 components but since you’re clever you decide to implement it as a single component and make it reusable as follows :</p><h3>Button With Text</h3><pre>// ignore_for_file: public_member_api_docs, sort_constructors_first<br>import &#39;package:flutter/material.dart&#39;;<br><br>class CustomButton extends StatelessWidget {<br>  final String? title;<br>  final Widget? child;<br>  final void Function() onTap;<br>  final Color? color;<br><br>  const CustomButton({<br>    Key? key,<br>    this.title,<br>    this.child,<br>    required this.onTap,<br>    this.color,<br>  })  : assert(title != null || child != null),<br>        super(key: key);<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return TextButton(<br>      onPressed: onTap,<br>      style: TextButton.styleFrom(<br>        backgroundColor: color ?? const Color(0xff006836),<br>        shape: RoundedRectangleBorder(<br>          borderRadius: BorderRadius.circular(6),<br>        ),<br>      ),<br>      child: title != null<br>          ? Text(<br>              title!,<br>              style: const TextStyle(color: Colors.white),<br>            )<br>          : child!,<br>    );<br>  }<br>}</pre><p>Then, you make a new component for the Button With Icon by reusing the old component as :</p><h3>Button With Icon</h3><pre>class CustomButtonWithIcon extends StatelessWidget {<br>  final IconData icon;<br>  final String title;<br>  final Color? color;<br>  final void Function() onTap;<br><br>  const CustomButtonWithIcon({<br>    Key? key,<br>    required this.icon,<br>    required this.title,<br>    required this.onTap,<br>    this.color,<br>  }) : super(key: key);<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return CustomButton(<br>      color: color,<br>      onTap: onTap,<br>      child: Row(<br>        mainAxisAlignment: MainAxisAlignment.center,<br>        children: [<br>          Icon(icon, color: Colors.white),<br>          const SizedBox(width: 4),<br>          Text(<br>            title,<br>            style: const TextStyle(color: Colors.white),<br>          ),<br>          const SizedBox(width: 4),<br>          const Icon(<br>            Icons.chevron_right,<br>            color: Colors.white,<br>          ),<br>        ],<br>      ),<br>    );<br>  }<br>}</pre><p>Implementing it this way works but by writing CustomButton and passing it all the parameters, you have essentially duplicated code.</p><p>For the small number of parameters, this might not be a problem but for large numbers of parameters (and this will happen specially when working with Flutter) the number of duplications is unreasonable.</p><p>So, what to do then?</p><h3>There is Another Way !!!</h3><p>This is where the power of Object Oriented Programming comes in.</p><p>To remove these duplications we can implement these buttons by using inheritance as follows :</p><pre>abstract class AppButton extends TextButton {<br>  AppButton({<br>    super.key,<br>    required super.onPressed,<br>    required super.child,<br>    Color? color,<br>  }) : super(<br>          style: TextButton.styleFrom(<br>            backgroundColor: color ?? const Color(0xff006836),<br>            shape: RoundedRectangleBorder(<br>              borderRadius: BorderRadius.circular(6),<br>            ),<br>          ),<br>        );<br>}</pre><h3>Button with Text</h3><pre>class CustomButton extends AppButton {<br> CustomButton({<br> super.key,<br> required super.onPressed,<br> required String title,<br> super.color,<br> }) : super(<br> child: Text(<br> title,<br> style: const TextStyle(color: Colors.white),<br> ),<br> );<br>}</pre><h3>Button With Icon</h3><pre>class CustomButtonWithIcon extends AppButton {<br>  CustomButtonWithIcon({<br>    super.key,<br>    required super.onPressed,<br>    required String title,<br>    required IconData icon,<br>    super.color,<br>  }) : super(<br>          child: Row(<br>            mainAxisAlignment: MainAxisAlignment.center,<br>            children: [<br>              Icon(icon, color: Colors.white),<br>              const SizedBox(width: 4),<br>              Text(<br>                title,<br>                style: const TextStyle(color: Colors.white),<br>              ),<br>              const SizedBox(width: 4),<br>              const Icon(<br>                Icons.chevron_right,<br>                color: Colors.white,<br>              ),<br>            ],<br>          ),<br>        );<br>}</pre><p>All this is made possible because we build UI components in Flutter using Dart classes.</p><p>This is just a simple idea.</p><p>However, <br>This idea is very powerful, it’s not only useful for simple and stateless UI components like buttons but it can be used for Stateful Screens. The only difference with implementing such idea with Stateful Screens is that the base abstraction (i.e. The abstract class) <br>will extend State&lt;T extends StatefulWidget&gt; instead of extending StatelessWidget .</p><p>This technique can help reduce a lot of duplicate code and make the code cleaner.</p><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/leveraging-power-of-object-oriented-programming-to-build-uis-with-flutter-d93e9a1f3c75"><em>https://medium.com</em></a><em> on September 6, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=86230aa5b30" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Text Recognition from Image (OCR) in Flutter]]></title>
            <link>https://medium.com/@raralabs/text-recognition-from-image-ocr-in-flutter-202c27c871a9?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/202c27c871a9</guid>
            <category><![CDATA[text-recognition]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[computer-vision]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Sun, 02 Jul 2023 11:09:41 GMT</pubDate>
            <atom:updated>2023-07-04T10:09:26.547Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/88/0*ibl4u89lZIDuiQOw.jpeg" /></figure><blockquote>This article only covers text recognition in device without use of any cloud platforms.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*JMNUHglm87TtcJC6" /></figure><p>Flutter is Google’s Solution for building blazing-fast UIs.</p><p>Since Flutter is simply only for UIs all the complex stuff like Text Recognition if done in native platforms. So, all we need to do is call the native APIs.</p><p>There are packages in Flutter that help us to call these native APIs.<br>All we need to do in Flutter is obtain the image and then call the native APIs via the package and pass the image.</p><p>This article covers 2 packages to recognize text :<br>1. <a href="https://pub.dev/packages/google_mlkit_text_recognition">google_mlkit_text_recognition</a> <br>2. <a href="https://pub.dev/packages/flutter_tesseract_ocr">flutter_tesseract_ocr</a></p><p>The code for the project can be found <a href="https://github.com/SushanShakya/text_recognition_flutter">here</a>.</p><p><a href="https://github.com/SushanShakya/text_recognition_flutter">GitHub - SushanShakya/text_recognition_flutter</a></p><p>Before we deal with these packages, let’s see how we can get the image for processing.</p><h3>Obtaining Image</h3><p>Add the dependency for image_picker :</p><pre>dependencies:<br>  flutter:<br>    sdk: flutter<br>  ...<br>  image_picker: ^1.0.0<br>  ...</pre><p>We also need to update the minSdkVersion to 21 or higher inside app/build.gradle :</p><pre>defaultConfig {<br>  ...<br>  minSdkVersion 21<br>  ...<br>}</pre><p>Now,<br>We create a page with a floating button to pick image as follows :</p><pre>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:image_picker/image_picker.dart&#39;;<br><br>class Home extends StatefulWidget {<br>  const Home({super.key});<br><br>  @override<br>  State&lt;Home&gt; createState() =&gt; _HomeState();<br>}<br><br>class _HomeState extends State&lt;Home&gt; {<br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      appBar: AppBar(<br>        title: const Text(&#39;Text Recognition&#39;),<br>      ),<br>      floatingActionButton: FloatingActionButton(<br>        onPressed: () {},<br>        child: const Icon(Icons.add),<br>      ),<br>      body: Container(),<br>    );<br>  }<br>}</pre><p>When, the user taps on the button we want to show them options to get image from camera or gallery. For this we can create a separate function to render a Alert Dialog as follows :</p><pre>import &#39;package:flutter/material.dart&#39;;<br><br>Widget imagePickAlert({<br>  void Function()? onCameraPressed,<br>  void Function()? onGalleryPressed,<br>}) {<br>  return AlertDialog(<br>    title: const Text(<br>      &quot;Pick a source:&quot;,<br>    ),<br>    content: Column(<br>      mainAxisSize: MainAxisSize.min,<br>      children: [<br>        ListTile(<br>          leading: const Icon(Icons.camera_alt),<br>          title: const Text(<br>            &quot;Camera&quot;,<br>          ),<br>          onTap: onCameraPressed,<br>        ),<br>        ListTile(<br>          leading: const Icon(Icons.image),<br>          title: const Text(<br>            &quot;Gallery&quot;,<br>          ),<br>          onTap: onGalleryPressed,<br>        ),<br>      ],<br>    ),<br>  );<br>}</pre><p>Then,<br>When the floating button is tapped, we can show the dialog as :</p><pre>...<br>      floatingActionButton: FloatingActionButton(<br>        onPressed: () {<br>          showDialog(<br>            context: context,<br>            builder: (context) =&gt; imagePickAlert(<br>              onCameraPressed: () {<br>                // Pick image from Camera<br>              },<br>              onGalleryPressed: () {<br>                // Pick image from Gallery<br>              },<br>            ),<br>          );<br>        },<br>        child: const Icon(Icons.add),<br>      ),<br>...</pre><p>We can get image from camera or gallery by using image_picker package as follows :</p><pre>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:image_picker/image_picker.dart&#39;;<br><br>import &#39;image_widget.dart&#39;;<br><br>class Home extends StatefulWidget {<br>  ...<br>}<br><br>class _HomeState extends State&lt;Home&gt; {<br>  late ImagePicker _picker;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    _picker = ImagePicker();<br>  }<br><br>  Future&lt;String?&gt; obtainImage(ImageSource source) async {<br>    final file = await _picker.pickImage(source: source);<br>    return file?.path;<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      ...<br>    );<br>  }<br>}</pre><p>Here,<br>obtainImage() function can be called to get image.<br>This can be done as follows :</p><pre>...<br>showDialog(<br>  context: context,<br>  builder: (context) =&gt; imagePickAlert(<br>  onCameraPressed: () async {<br>    final imgPath = await obtainImage(ImageSource.camera);<br>    ...<br>    Navigator.of(context).pop();<br>  },<br>  onGalleryPressed: () {<br>    final imgPath = await obtainImage(ImageSource.gallery);<br>    ...                <br>    Navigator.of(context).pop();<br>  },<br> ),<br>);<br>...</pre><p>Now,<br>We can start working on processing the image as we’re able to get image using image_picker .</p><p>For processing image to recognize text we’ll create our own abstraction as :</p><pre>abstract class ITextRecognizer {<br> Future&lt;String&gt; processImage(String imgPath);<br>}</pre><h3>Using [google_mlkit_text_recognition]</h3><p>Add dependency for google_mlkit_text_recognition :</p><pre>dependencies:<br>  flutter:<br>    sdk: flutter<br>  <br>  ...<br>  google_mlkit_text_recognition: ^0.8.1<br>  ...</pre><p>Now,<br>We create a MLKitTextRecognizer for recognizing text as :</p><pre>import &#39;dart:io&#39;;<br>import &#39;package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart&#39;;<br>import &#39;interface/text_recognizer.dart&#39;;<br><br>class MLKitTextRecognizer extends ITextRecognizer {<br>  late TextRecognizer recognizer;<br><br>  MLKitTextRecognizer() {<br>    recognizer = TextRecognizer();<br>  }<br><br>  void dispose() {<br>    recognizer.close();<br>  }<br><br>  @override<br>  Future&lt;String&gt; processImage(String imgPath) async {<br>    final image = InputImage.fromFile(File(imgPath));<br>    final recognized = await recognizer.processImage(image);<br>    return recognized.text;<br>  }<br>}</pre><p>We can create an instance for it as :</p><pre>...<br><br>class _HomeState extends State&lt;Home&gt; {<br>  late ImagePicker _picker;<br>  late ITextRecognizer _recognizer;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    _picker = ImagePicker();<br>    _recognizer = MLKitTextRecognizer();<br>  }<br><br>  @override<br>  void dispose() {<br>    super.dispose();<br>    if(_recognizer is MLKitTextRecognizer) {<br>      (_recognizer as MLKitTextRecognizer).dispose();<br>    }<br>  }<br><br>  Future&lt;String?&gt; obtainImage(ImageSource source) async {<br>    final file = await _picker.pickImage(source: source);<br>    return file?.path;<br>  }<br><br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      ...<br>    );<br>  }<br>}<br><br>...</pre><p>We then create a function to process the image as :</p><pre>....<br><br>class _HomeState extends State&lt;Home&gt; {<br><br>  ...<br>  void processImage(String imgPath) async{<br>    final recognizedText = await _recognizer.processImage(imgPath);<br>    ...<br>  }<br>  ...<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      ...<br>    );<br>  }<br>}<br><br>....</pre><p>Here,<br>After we have processed the image, we would like to show it in the UI.<br>For showing it in our UI we need 2 piece of information : <br>1. The Image File<br>2. The Recognized Text</p><p>So, we’ll create a data class for it as :</p><pre>class RecognitionResponse {<br>  final String imgPath;<br>  final String recognizedText;<br><br>  RecognitionResponse({<br>    required this.imgPath,<br>    required this.recognizedText,<br>  });<br><br>  @override<br>  bool operator ==(covariant RecognitionResponse other) {<br>    if (identical(this, other)) return true;<br><br>    return other.imgPath == imgPath &amp;&amp; other.recognizedText == recognizedText;<br>  }<br><br>  @override<br>  int get hashCode =&gt; imgPath.hashCode ^ recognizedText.hashCode;<br>}</pre><p>Then, we create a variable of type RecognitionResponse in HomeState as :</p><pre>class _HomeState extends State&lt;Home&gt; {<br>  <br>  ...<br>  RecognitionResponse? _response;<br>  ...<br><br>  @override<br>  Widget build(BuildContext context) {<br>    ...<br>  }<br>}</pre><p>Now,<br>We can set this variable from processImage() as :</p><pre> ...<br><br>  void processImage(String imgPath) async{<br>    final recognizedText = await _recognizer.processImage(imgPath);<br>    setState(() {<br>      _response = RecognitionResponse(imgPath: imgPath, recognizedText: recognizedText);<br>    });<br>  }<br><br>  ...</pre><p>We will call processImage() when image is selected.</p><pre>...<br>showDialog(<br>  context: context,<br>  builder: (context) =&gt; imagePickAlert(<br>  onCameraPressed: () async {<br>    final imgPath = await obtainImage(ImageSource.camera);<br>    if(imgPath == null) return;<br>    processImage(imgPath);<br>    Navigator.of(context).pop();<br>  },<br>  onGalleryPressed: () {<br>    final imgPath = await obtainImage(ImageSource.gallery);<br>    if(imgPath == null) return;<br>    processImage(imgPath);<br>    Navigator.of(context).pop();<br>  },<br> ),<br>);<br>...</pre><p>Finally,<br>We need to show the image and recognized text in the UI when _response variable is not null.</p><pre>class _HomeState extends State&lt;Home&gt; {<br><br>  ...<br><br>  RecognitionResponse? _response;<br><br>  ...<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      appBar: AppBar(<br>        title: const Text(&#39;Text Recognition&#39;),<br>      ),<br>      floatingActionButton: FloatingActionButton(<br>        onPressed: () {<br>          showDialog(<br>            context: context,<br>            builder: (context) =&gt; imagePickAlert(<br>              onCameraPressed: () async {<br>                final imgPath = await obtainImage(ImageSource.camera);<br>                if (imgPath == null) return;<br>                processImage(imgPath);<br>                Navigator.of(context).pop();<br>              },<br>              onGalleryPressed: () async {<br>                final imgPath = await obtainImage(ImageSource.gallery);<br>                if (imgPath == null) return;<br>                processImage(imgPath);<br>                Navigator.of(context).pop();<br>              },<br>            ),<br>          );<br>        },<br>        child: const Icon(Icons.add),<br>      ),<br>      body: _response == null<br>          ? const Center(<br>              child: Text(&#39;Pick image to continue&#39;),<br>            )<br>          : ListView(<br>              children: [<br>                SizedBox(<br>                  height: MediaQuery.of(context).size.width,<br>                  width: MediaQuery.of(context).size.width,<br>                  child: Image.file(File(_response!.imgPath)),<br>                ),<br>                Padding(<br>                    padding: const EdgeInsets.all(16),<br>                    child: Column(<br>                      crossAxisAlignment: CrossAxisAlignment.start,<br>                      children: [<br>                        Row(<br>                          children: [<br>                            Expanded(<br>                              child: Text(<br>                                &quot;Recognized Text&quot;,<br>                                style: Theme.of(context).textTheme.titleLarge,<br>                              ),<br>                            ),<br>                            IconButton(<br>                              onPressed: () {<br>                                Clipboard.setData(<br>                                  ClipboardData(<br>                                      text: _response!.recognizedText),<br>                                );<br>                                ScaffoldMessenger.of(context).showSnackBar(<br>                                  const SnackBar(<br>                                    content: Text(&#39;Copied to Clipboard&#39;),<br>                                  ),<br>                                );<br>                              },<br>                              icon: const Icon(Icons.copy),<br>                            ),<br>                          ],<br>                        ),<br>                        const SizedBox(height: 10),<br>                        Text(_response!.recognizedText),<br>                      ],<br>                    )),<br>              ],<br>            ),<br>    );<br>  }<br>}</pre><p>Ta-da, your app is now able to recognize text from image.</p><h3>Using [flutter_tesseract_ocr]</h3><p>Add dependency for flutter_tesseract_ocr as :</p><pre>dependencies:<br>  flutter:<br>    sdk: flutter<br>  ...<br>  flutter_tesseract_ocr: ^0.4.23<br>  ...</pre><p>Then,<br>Download the trained data for English language from <a href="https://github.com/tesseract-ocr/tessdata">here</a>.<br>The file is called <a href="https://github.com/tesseract-ocr/tessdata/blob/main/eng.traineddata">eng.traineddata</a></p><p>Once downloaded, copy the file to assets/tessdata as :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/334/0*BKD8XVzlDbtu1N2f.png" /></figure><p>Now,<br>We need to create a configuration file in assets/tessdata_config.json as :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/345/0*MEemkt05daEcnvVb.png" /></figure><p>Inside tessdata_config.json we add following content:</p><pre>{<br>    &quot;files&quot;: [<br>        &quot;eng.traineddata&quot;<br>    ]<br>}</pre><p>Then, we have to include the asset file into the flutter project by adding entry to pubspec.yaml :</p><pre>...<br>  assets:<br>    - assets/<br>    - assets/tessdata/<br>...</pre><p>Now,<br>We create TesseractTextRecognizer for recognizing text as :</p><pre>import &#39;package:flutter_tesseract_ocr/android_ios.dart&#39;;<br>import &#39;package:ml_kit_test/recognition/interface/text_recognizer.dart&#39;;<br><br>class TesseractTextRecognizer extends ITextRecognizer {<br> @override<br> Future&lt;String&gt; processImage(String imgPath) async {<br> final res = await FlutterTesseractOcr.extractText(imgPath, args: {<br> &quot;psm&quot;: &quot;4&quot;,<br> &quot;preserve_interword_spaces&quot;: &quot;1&quot;,<br> });<br> return res;<br> }<br>}</pre><p>We can use the same UI as we used for google_mlkit_text_recognition .</p><p>The only thing we need to change is the initialization of _recognizer as :</p><pre> @override<br>  void initState() {<br>    super.initState();<br>    ...<br>    _recognizer = TesseractTextRecognizer();<br>    ...<br>  }</pre><pre>import &#39;dart:io&#39;;<br><br>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:flutter/services.dart&#39;;<br>import &#39;package:image_picker/image_picker.dart&#39;;<br>import &#39;package:ml_kit_test/model/recognition_response.dart&#39;;<br>import &#39;package:ml_kit_test/recognition/interface/text_recognizer.dart&#39;;<br>import &#39;package:ml_kit_test/recognition/text_recognition_service.dart&#39;;<br><br>import &#39;image_widget.dart&#39;;<br><br>class Home extends StatefulWidget {<br>  const Home({super.key});<br><br>  @override<br>  State&lt;Home&gt; createState() =&gt; _HomeState();<br>}<br><br>class _HomeState extends State&lt;Home&gt; {<br>  late ImagePicker _picker;<br>  late ITextRecognizer _recognizer;<br><br>  RecognitionResponse? _response;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    _picker = ImagePicker();<br>    _recognizer = TesseractTextRecognizer();<br>  }<br><br>  @override<br>  void dispose() {<br>    super.dispose();<br>    if (_recognizer is MLKitTextRecognizer) {<br>      (_recognizer as MLKitTextRecognizer).dispose();<br>    }<br>  }<br><br>  void processImage(String imgPath) async {<br>    final recognizedText = await _recognizer.processImage(imgPath);<br>    setState(() {<br>      _response =<br>          RecognitionResponse(imgPath: imgPath, recognizedText: recognizedText);<br>    });<br>  }<br><br>  Future&lt;String?&gt; obtainImage(ImageSource source) async {<br>    final file = await _picker.pickImage(source: source);<br>    return file?.path;<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      appBar: AppBar(<br>        title: const Text(&#39;Text Recognition&#39;),<br>      ),<br>      floatingActionButton: FloatingActionButton(<br>        onPressed: () {<br>          showDialog(<br>            context: context,<br>            builder: (context) =&gt; imagePickAlert(<br>              onCameraPressed: () async {<br>                final imgPath = await obtainImage(ImageSource.camera);<br>                if (imgPath == null) return;<br>                processImage(imgPath);<br>                Navigator.of(context).pop();<br>              },<br>              onGalleryPressed: () async {<br>                final imgPath = await obtainImage(ImageSource.gallery);<br>                if (imgPath == null) return;<br>                processImage(imgPath);<br>                Navigator.of(context).pop();<br>              },<br>            ),<br>          );<br>        },<br>        child: const Icon(Icons.add),<br>      ),<br>      body: _response == null<br>          ? const Center(<br>              child: Text(&#39;Pick image to continue&#39;),<br>            )<br>          : ListView(<br>              children: [<br>                SizedBox(<br>                  height: MediaQuery.of(context).size.width,<br>                  width: MediaQuery.of(context).size.width,<br>                  child: Image.file(File(_response!.imgPath)),<br>                ),<br>                Padding(<br>                    padding: const EdgeInsets.all(16),<br>                    child: Column(<br>                      crossAxisAlignment: CrossAxisAlignment.start,<br>                      children: [<br>                        Row(<br>                          children: [<br>                            Expanded(<br>                              child: Text(<br>                                &quot;Recognized Text&quot;,<br>                                style: Theme.of(context).textTheme.titleLarge,<br>                              ),<br>                            ),<br>                            IconButton(<br>                              onPressed: () {<br>                                Clipboard.setData(<br>                                  ClipboardData(<br>                                      text: _response!.recognizedText),<br>                                );<br>                                ScaffoldMessenger.of(context).showSnackBar(<br>                                  const SnackBar(<br>                                    content: Text(&#39;Copied to Clipboard&#39;),<br>                                  ),<br>                                );<br>                              },<br>                              icon: const Icon(Icons.copy),<br>                            ),<br>                          ],<br>                        ),<br>                        const SizedBox(height: 10),<br>                        Text(_response!.recognizedText),<br>                      ],<br>                    )),<br>              ],<br>            ),<br>    );<br>  }<br>}</pre><p>Boom,<br>We can use the same UI but handle image processing using 2 different packages because we created our own abstraction for text recognition.</p><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/text-recognition-from-image-ocr-in-flutter-d9b35190d824"><em>https://medium.com</em></a><em> on July 2, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=202c27c871a9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Advanced Git Concepts: Rebasing]]></title>
            <link>https://medium.com/@raralabs/advanced-git-concepts-rebasing-fc21291250f8?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/fc21291250f8</guid>
            <category><![CDATA[collaboration]]></category>
            <category><![CDATA[git]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Thu, 29 Jun 2023 05:50:21 GMT</pubDate>
            <atom:updated>2023-07-25T07:08:09.593Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/88/0*AbF6mHH34DL7fg9I.jpeg" /></figure><blockquote>Article assumes user has some familiarity with git</blockquote><p>Consider that we have 2 developers working on 2 different features,<br>the Git tree in this case will look as follows :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*jaoQndvhqwfDXcOy.png" /></figure><p>Here,<br>These 2 developers are working parallelly on 2 different features.<br>We can think of feature1 and feature2 as being 2 parallel worlds or different timelines of the future.</p><p>In fact, the dev branch doesn&#39;t have any idea about feature1 or feature2 .</p><p>Now,<br>Let’s say that both feature1 and feature2 are completed at the same time.</p><p>In such case, <br>Developer of feature1 will create a pull request from feature1 to dev .<br>Developer of feature2 will create a pull request from feature2 to dev .</p><p>This point is time is the decisive moment.</p><p>dev is our main timeline, we can pick to merge feature1 or feature2 .</p><h3>Picking feature1</h3><p>Let’s say we pick to merge feature1 first then our main timeline will be that, our project state was in dev then feature1 was added. The git tree will look like :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*v-YfsCv7-Q4Lm_pb.png" /></figure><p>Here,<br>dev now knows that feature1 has happened.<br>It doesn&#39;t know anything about feature2 .</p><p>feature2 knows about old dev but doesn&#39;t know anything about feature1 being part of dev .</p><p>If we want the feature2 branch to know about changes and addition of feature1 to dev then we can rebase feature2 .</p><h3>Rebasing</h3><p>The idea of rebasing is simple.<br>We’ll break the feature2 branch from it&#39;s origination point and then attach it somewhere else. Let&#39;s look at the tree to understand this :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*99-AobuyFamaS8kt.png" /></figure><p>To break it off it’s base and attach it to the new dev commits, we can use the following command :</p><blockquote>(feature2) ~ git rebase dev</blockquote><blockquote>(feature2) indicates that we are currently in feature2 branch</blockquote><p>This command then breaks off the base of feature2 and attaches it to the dev . We can visualize it as :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*WHVvBoA1ya6gKdds.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*8HvoVj_vWZkyUlH2.png" /></figure><p>Now,<br>feature2 knows about the existence of feature1 which is merged into dev .</p><p>This is one of the powers of rebasing.</p><p>When rebasing we might have merge conflicts if feature1 and feature2 branch have changes to the same file. In such case, we resolve the merge conflicts, then add the resolved files as :</p><blockquote>(feature2) (rebasing) ~ git add<em> .</em></blockquote><p>Then, we continue with rebasing as :</p><blockquote>(feature2) (rebasing) ~ git rebase — continue</blockquote><p>If we want to cancel the rebase, we use :</p><blockquote>(feature2) (rebasing) ~ git rebase — abort</blockquote><p>We can do the similar thing if we wanted to merge feature2 before feature1 .</p><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/advanced-git-concepts-rebasing-91dee596fa34"><em>https://medium.com</em></a><em> on June 29, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fc21291250f8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integrating Connect IPS in Flutter.]]></title>
            <link>https://medium.com/@raralabs/integrating-connect-ips-in-flutter-3350085bb7af?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/3350085bb7af</guid>
            <category><![CDATA[payment-gateway]]></category>
            <category><![CDATA[connectips]]></category>
            <category><![CDATA[flutter]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Wed, 21 Jun 2023 07:22:55 GMT</pubDate>
            <atom:updated>2023-06-29T05:53:58.700Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/88/0*j1jcbTtlrMpSUELK.jpeg" /></figure><blockquote>Article uses BLoC as a state management solution.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*A-7b7iZU7bQQRQp5.png" /></figure><p>To integrate Connect IPS in Flutter we need to use WebView.</p><p>Connect IPS is very different from other payment gateways like <strong>E-sewa</strong> or <strong>Khalti</strong> because it sends out response as HTML.<br>So, to work with Connect IPS we need to use WebView in Flutter.</p><p>Before we begin integrating Connect IPS we must first establish how our software is going to handle payments. Connect IPS requires an encrypted token to be generated. It is a good idea to generate this token from the backend.<br>We also want all the params required to start the payment to be generated by the backend and the frontend (i.e The Mobile App) will only send a request to the Connect IPS server to start the payment.</p><p>The following will be the flow for the app :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*kdE9CLOhA_rxJssE.png" /></figure><p>Here,<br>We’re only interested in Connect IPS payment so, following is the flow we want to focus on :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*M8_DpjBLYRuSCGOg.png" /></figure><h3># Steps to integrate</h3><h3>Step #1</h3><p>Add the following dependency to pubspec.yaml :</p><pre>dependencies:<br> flutter:<br> sdk: flutter<br> webview_flutter: ^4.2.0</pre><h3>Step #2</h3><p>Create a page for Connect IPS payment :</p><pre>// ignore_for_file: public_member_api_docs, sort_constructors_first<br>import &#39;dart:convert&#39;;<br><br>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:webview_flutter/webview_flutter.dart&#39;;<br><br>class PaymentView extends StatefulWidget {<br>  final int purchaseId;<br><br>  const PaymentView({<br>    Key? key,<br>    required this.purchaseId,<br>  }) : super(key: key);<br><br>  @override<br>  State&lt;PaymentView&gt; createState() =&gt; _PaymentViewState();<br>}<br><br>class _PaymentViewState extends State&lt;PaymentView&gt; {<br>  late WebViewController controller;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    controller = WebViewController()<br>      ..setJavaScriptMode(JavaScriptMode.unrestricted)<br>      ..setBackgroundColor(const Color(0xffffffff))<br>      ..setNavigationDelegate(<br>        NavigationDelegate(<br>          onUrlChange: (value) {<br>            print(&#39;---- URL Change&#39;);<br>            print(value.url);<br>          },<br>          onProgress: (int progress) {<br>            // Update loading bar.<br>          },<br>          onPageStarted: (String url) {<br>            print(&#39;------ URL&#39;);<br>            print(url);<br>          },<br>          onPageFinished: (String url) {},<br>          onWebResourceError: (WebResourceError error) {},<br>          onNavigationRequest: (NavigationRequest request) {<br>            return NavigationDecision.navigate;<br>          },<br>        ),<br>      );<br>  }<br><br>  void initWebView(String content) {<br>    final uri = Uri.dataFromString(<br>      content,<br>      mimeType: &#39;text/html&#39;,<br>      encoding: Encoding.getByName(&#39;utf-8&#39;),<br>    );<br><br>    controller.loadRequest(uri);<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      appBar: AppBar(<br>        title: const Text(&#39;Payment&#39;),<br>      ),<br>      body: WebViewWidget(<br>        controller: controller,<br>      ),<br>    );<br>  }<br>}</pre><p>As we can see, a WebView is required because there is no other way to integrate Connect IPS except by using WebView .</p><h3>Step #3</h3><p>Create a mechanism to get the required params from the backend.</p><p>For this, we will follow the following flow :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*fcKHZS6WzDJ_sjH9.png" /></figure><p>The following is the json structure in which the backend will return the params :</p><pre>{<br>  &quot;MERCHANTID&quot; : &quot;&lt;merchant-id&gt;&quot;,<br>  &quot;APPID&quot; : &quot;&lt;app-id&gt;&quot;,<br>  &quot;APPNAME&quot; : &quot;&lt;app-name&gt;&quot;,<br>  &quot;TXNID&quot; : &quot;&lt;transaction-id&gt;&quot;,<br>  &quot;TXNDATE&quot; : &quot;&lt;transaction-date&gt;&quot;,<br>  &quot;TXNCRNCY&quot; : &quot;&lt;transaction-currency&gt;&quot;,<br>  &quot;TXNAMT&quot; : &quot;&lt;transaction-amount&gt;&quot;,<br>  &quot;REFERENCEID&quot;: &quot;&lt;reference-id&gt;&quot;,<br>  &quot;REMARKS&quot;: &quot;&lt;remarks&gt;&quot;,<br>  &quot;PARTICULARS&quot;: &quot;&lt;particulars&gt;&quot;,<br>  &quot;TOKEN&quot;: &quot;&lt;encrypted-token&gt;&quot;,<br>}</pre><p>Then,<br>We will create an HTML string to send request to Connect IPS server to start the payment.</p><p>We will create a Cubit for this purpose as :</p><pre>import &#39;package:bloc/bloc.dart&#39;;<br>import &#39;package:equatable/equatable.dart&#39;;<br>import &#39;./dio.dart&#39;;<br><br>part &#39;connect_ips_payment_state.dart&#39;;<br><br>class ConnectIpsPaymentCubit extends Cubit&lt;ConnectIpsPaymentState&gt; {<br>  ConnectIpsPaymentCubit() : super(ConnectIpsPaymentInitial());<br><br>  Future&lt;Map&lt;String, dynamic&gt;&gt; _getParams(int purchaseId) async {<br>    final res = await dio.post(&#39;/getParams&#39;, dat: {<br>      &quot;purchaseId&quot;: purchaseId,<br>    });<br>    return res.data;<br>  }<br><br>  Future&lt;String&gt; _getHtmlString(int purchaseId) async {<br>    final param = await _getParams(purchaseId);<br>    return &quot;&quot;&quot;<br>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br><br>&lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot;&gt;<br>    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&gt;<br>    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;<br>    &lt;title&gt;Document&lt;/title&gt;<br>&lt;/head&gt;<br><br>&lt;body&gt;<br>    &lt;script&gt;<br>        const form = document.createElement(&quot;FORM&quot;);<br>        form.setAttribute(&quot;action&quot;, &quot;https://uat.connectips.com/connectipswebgw/loginpage&quot;);<br>        form.setAttribute(&quot;target&quot;, &quot;&quot;);<br>        form.setAttribute(&quot;method&quot;, &quot;POST&quot;);<br><br>        const merchantIdElement = document.createElement(&quot;INPUT&quot;);<br>        merchantIdElement.setAttribute(&quot;name&quot;, &quot;MERCHANTID&quot;);<br>        merchantIdElement.setAttribute(&quot;value&quot;, &quot;${param[&#39;MERCHANTID&#39;]}&quot;);<br>        merchantIdElement.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(merchantIdElement);<br><br>        const appId = document.createElement(&quot;INPUT&quot;);<br>        appId.setAttribute(&quot;name&quot;, &quot;APPID&quot;);<br>        appId.setAttribute(&quot;value&quot;, &quot;${param[&#39;APPID&#39;]}&quot;);<br>        appId.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(appId);<br><br>        const appName = document.createElement(&quot;INPUT&quot;);<br>        appName.setAttribute(&quot;name&quot;, &quot;APPNAME&quot;);<br>        appName.setAttribute(&quot;value&quot;, &quot;${param[&#39;APPNAME&#39;]}&quot;);<br>        appName.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(appName);<br><br>        const txnId = document.createElement(&quot;INPUT&quot;);<br>        txnId.setAttribute(&quot;name&quot;, &quot;TXNID&quot;);<br>        txnId.setAttribute(&quot;value&quot;, &quot;${param[&#39;TXNID&#39;]}&quot;);<br>        txnId.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(txnId);<br><br>        const txnDate = document.createElement(&quot;INPUT&quot;);<br>        txnDate.setAttribute(&quot;name&quot;, &quot;TXNDATE&quot;);<br>        txnDate.setAttribute(&quot;value&quot;, &quot;${param[&#39;TXNDATE&#39;]}&quot;);<br>        txnDate.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(txnDate);<br><br>        const txnCurrency = document.createElement(&quot;INPUT&quot;);<br>        txnCurrency.setAttribute(&quot;name&quot;, &quot;TXNCRNCY&quot;);<br>        txnCurrency.setAttribute(&quot;value&quot;, &quot;${param[&#39;TXNCRNCY&#39;]}&quot;);<br>        txnCurrency.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(txnCurrency);<br><br>        const txnAmount = document.createElement(&quot;INPUT&quot;);<br>        txnAmount.setAttribute(&quot;name&quot;, &quot;TXNAMT&quot;);<br>        txnAmount.setAttribute(&quot;value&quot;, &quot;${param[&#39;TXNAMT&#39;]}&quot;);<br>        txnAmount.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(txnAmount);<br><br>        const referenceId = document.createElement(&quot;INPUT&quot;);<br>        referenceId.setAttribute(&quot;name&quot;, &quot;REFERENCEID&quot;);<br>        referenceId.setAttribute(&quot;value&quot;, &quot;${param[&#39;REFERENCEID&#39;]}&quot;);<br>        referenceId.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(referenceId);<br><br>        const remarks = document.createElement(&quot;INPUT&quot;);<br>        remarks.setAttribute(&quot;name&quot;, &quot;REMARKS&quot;);<br>        remarks.setAttribute(&quot;value&quot;, &quot;${param[&#39;REMARKS&#39;]}&quot;);<br>        remarks.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(remarks);<br><br>        const particulars = document.createElement(&quot;INPUT&quot;);<br>        particulars.setAttribute(&quot;name&quot;, &quot;PARTICULARS&quot;);<br>        particulars.setAttribute(&quot;value&quot;, &quot;${param[&#39;PARTICULARS&#39;]}&quot;);<br>        particulars.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(particulars);<br><br>        const token = document.createElement(&quot;INPUT&quot;);<br>        token.setAttribute(&quot;name&quot;, &quot;TOKEN&quot;);<br>        token.setAttribute(&quot;value&quot;, &quot;${param[&#39;TOKEN&#39;]}&quot;);<br>        token.setAttribute(&quot;type&quot;, &quot;hidden&quot;);<br>        form.appendChild(token);<br><br>        // console.log(form);<br><br>        document.body.appendChild(form);<br>        form.submit();<br>    &lt;/script&gt;<br>&lt;/body&gt;<br><br>&lt;/html&gt;<br>&quot;&quot;&quot;;<br>  }<br><br>  Future&lt;void&gt; initiate(int purchaseId) async {<br>    emit(ConnectIpsPaymentLoading());<br>    try {<br>      final data = await _getHtmlString();<br>      emit(ConnectIpsPaymentLoaded(htmlString: data));<br>    } catch (e) {<br>      emit(const ConnectIpsPaymentFailed(message: &quot;Failed&quot;));<br>    }<br>  }<br>}</pre><blockquote>Note : <br>It is important to create a form in HTML and send the request otherwise it won’t work.</blockquote><h3>Step #4</h3><p>Now,<br>We can use the cubit to start the payment as follows :</p><pre>// ignore_for_file: public_member_api_docs, sort_constructors_first<br>import &#39;dart:convert&#39;;<br><br>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:flutter_bloc/flutter_bloc.dart&#39;;<br>import &#39;./connect_ips_payment_cubit.dart&#39;;<br>import &#39;package:webview_flutter/webview_flutter.dart&#39;;<br>import &#39;./show_error.dart&#39;;<br><br>class PaymentView extends StatefulWidget {<br>  final int purchaseId;<br><br>  const PaymentView({<br>    Key? key,<br>    required this.purchaseId,<br>  }) : super(key: key);<br><br>  @override<br>  State&lt;PaymentView&gt; createState() =&gt; _PaymentViewState();<br>}<br><br>class _PaymentViewState extends State&lt;PaymentView&gt; {<br>  late ConnectIpsPaymentCubit cubit;<br>  late WebViewController controller;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    cubit = ConnectIpsPaymentCubit()..initiate(widget.purchaseId);<br>    controller = WebViewController()<br>      ..setJavaScriptMode(JavaScriptMode.unrestricted)<br>      ..setBackgroundColor(const Color(0xffffffff))<br>      ..setNavigationDelegate(<br>        NavigationDelegate(<br>          onUrlChange: (value) {<br>            print(&#39;---- URL Change&#39;);<br>            print(value.url);<br>          },<br>          onProgress: (int progress) {<br>            // Update loading bar.<br>          },<br>          onPageStarted: (String url) {<br>            print(&#39;------ URL&#39;);<br>            print(url);<br>          },<br>          onPageFinished: (String url) {},<br>          onWebResourceError: (WebResourceError error) {},<br>          onNavigationRequest: (NavigationRequest request) {<br>            return NavigationDecision.navigate;<br>          },<br>        ),<br>      );<br>  }<br><br>  void initWebView(String content) {<br>    final uri = Uri.dataFromString(<br>      content,<br>      mimeType: &#39;text/html&#39;,<br>      encoding: Encoding.getByName(&#39;utf-8&#39;),<br>    );<br><br>    controller.loadRequest(uri);<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      appBar: AppBar(<br>        title: const Text(&#39;Payment&#39;),<br>      ),<br>      body: BlocConsumer&lt;ConnectIpsPaymentCubit, ConnectIpsPaymentState&gt;(<br>        bloc: cubit,<br>        listener: (context, state) {<br>          if (state is ConnectIpsPaymentFailed) {<br>            showErrorDialog(context);<br>            return;<br>          }<br>          if (state is ConnectIpsPaymentLoaded) {<br>            initWebView(state.htmlString);<br>          }<br>        },<br>        builder: (context, state) {<br>          if (state is ConnectIpsPaymentLoading) {<br>            return const Center(<br>              child: CircularProgressIndicator(),<br>            );<br>          }<br>          return WebViewWidget(<br>            controller: controller,<br>          );<br>        },<br>      ),<br>    );<br>  }<br>}</pre><p>When the params are loaded then the app will successfully show the login page for Connect IPS to furthur continue the payment.</p><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/integrating-connect-ips-in-flutter-898f7b0ecec5"><em>https://medium.com</em></a><em> on June 21, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3350085bb7af" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Managing Routes in Flutter like a PRO with animations.]]></title>
            <link>https://medium.com/@raralabs/managing-routes-in-flutter-like-a-pro-with-animations-1fefa2f966cd?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/1fefa2f966cd</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[routing]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Wed, 21 Jun 2023 07:20:56 GMT</pubDate>
            <atom:updated>2023-06-23T05:45:52.124Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><em>Article is written by demonstrating refactoring approach.</em></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*cTCqT7Pe5WZ2FHws" /></figure><p>If you search for managing routes in Flutter, all you get is a list of things you can do which usually always includes the following 3 :</p><h3>#1 Unnamed Routes</h3><pre>...<br><br>Navigator.of(context).push(<br>  MaterialPageRoute(<br>    builder: (c) =&gt; const HomeView(),<br>  ),<br>);<br><br>...</pre><h3>#2 Named Routes</h3><pre>...<br><br>return MaterialApp(<br>  title: &quot;Flutter Demo&quot;,<br>  routes: {<br>    &quot;home&quot;: (c) =&gt; const HomeView(),<br>    &quot;hotel&quot;: (c) =&gt; const HotelView(),<br>    &quot;hotelDetail&quot;: (c) =&gt; const HotelDetailView(),<br>  },<br>  home: const HomeView(),<br>);<br><br>...</pre><blockquote><em>Usage:</em></blockquote><pre>Navigator.of(context).pushNamed(&quot;home&quot;);</pre><h3>#3 onGenerateRoute</h3><pre>...<br><br>return MaterialApp(<br>  title: &quot;Flutter Demo&quot;,<br>  onGenerateRoute: Routes.onGenerateRoutes,<br>  initialRoute: Routes.home,<br>);<br><br>...</pre><pre>...<br><br>class Routes {<br>  static const String home = &quot;home&quot;;<br>  <br>  static Route onGenerateRoutes(RouteSettings settings) {<br>    Widget child;<br>    <br>    switch(settings.name) {<br>      case Routes.home:<br>        child = const HomeView();<br>        break;<br>      default:<br>        child = Container();<br>        break;<br>    }<br><br>    return MaterialPageRoute(builder: (c) =&gt; child);<br>  }<br>}<br>...</pre><blockquote><em>Usage:</em></blockquote><pre>...<br><br>Navigator.of(context).pushNamed(Routes.home);<br><br>...</pre><h3># The Problem</h3><p>Let’s say you decided to use the advanced route management technique #3 onGenerateRoute .</p><p>Since your app can have a lot of routes, you’ll end up with a Routes class that has a mountain load of cases for the switch statements. Which might look something like <a href="https://gist.github.com/SushanShakya/682ed5ab7bfee0b70b645dc26a56328a"><strong>this</strong></a>.</p><p>This makes it messy.<br>So, how do we make it clean and a bit more localized to it’s scope?</p><h3># The Solution</h3><p>We can look at another discipline and see how they manage routes.<br>For example Django manages routes by localizing all URLs for each module to urls.py .</p><p>We can learn from this idea.</p><p>Here’s how we separate our routes :</p><p>First, we’ll store our routes in a Map object like we did in #2 Named Routes .</p><pre>static Route onGenerateRoutes(RouteSettings settings) {<br>    var routes = {<br>      Routes.home: (context, settings) =&gt; const HomeView(),<br>    };<br>    try {<br>      final child = routes[settings.name];<br><br>      Widget builder(BuildContext c) =&gt; child!(c, settings);<br><br>      return MaterialPageRoute(<br>        builder: builder,<br>      );<br>    } catch (e) {<br>      throw const FormatException(&quot;--- Route doesn&#39;t exist&quot;);<br>    }<br>  }</pre><p>Now,<br>We can refactor this such that we get the routes from another function as follows :</p><pre>class Routes {<br>  static const String home = &quot;home&quot;;<br><br>  static Map&lt;String, RouteType&gt; _resolveRoutes() {<br>    return {<br>      Routes.home: (context, settings) =&gt; const HomeView(),<br>    };<br>  }<br><br>  static Route onGenerateRoutes(RouteSettings settings) {<br>    var routes = _resolveRoutes();<br>    try {<br>      final child = routes[settings.name];<br><br>      Widget builder(BuildContext c) =&gt; child!(c, settings);<br><br>      return MaterialPageRoute(<br>        builder: builder,<br>      );<br>    } catch (e) {<br>      throw const FormatException(&quot;--- Route doesn&#39;t exist&quot;);<br>    }<br>  }<br>}</pre><p>The _resolveRoutes() function can be refactored such that we have routes separated for each module.</p><p>What does that mean?<br>Well, let’s say you’re making a hotel booking app, then you might have the following pages :</p><ol><li>Login View</li><li>Signup View</li><li>Hotel Listing View</li><li>Hotel Detail View</li></ol><p>Here, Login View and Signup View are authorization routes so, we should group them into one route, Hotel Listing View and Hotel Detail View is associated with the Hotel so, it&#39;s a good idea to group them.</p><p>This can be done as follows :</p><pre>static _resolveRoutes() {<br>  var authRoutes = {<br>    Routes.login: (c, s) =&gt; const LoginView(),<br>    Routes.signup: (c, s) =&gt; const SignupView(),<br>  };<br><br>  var hotelRoutes = {<br>    Routes.hotel: (c, s) =&gt; const HotelView(),<br>    Routes.hotelDetail: (c, s) =&gt; const HotelDetailView(),<br>  };<br><br>  return {<br>    Routes.home: (context, settings) =&gt; const HomeView(),<br>    ...authRoutes,<br>    ...hotelRoutes,<br>  }</pre><p>Then, we can to a similar type of refactoring as before to get these routes from different files as :</p><pre>import &#39;../../modules/hotel/gui/views/login_view.dart&#39;;<br>import &#39;../../modules/hotel/gui/views/signup_view.dart&#39;;<br>import &#39;app_routes.dart&#39;;<br><br>var authRoutes = {<br>  Routes.login: (context, settings) =&gt; const LoginView(),<br>  Routes.signup: (context, settings) =&gt; const SignupView(),<br>};</pre><pre>import &#39;../../modules/hotel/gui/views/hotel_detail_view.dart&#39;;<br>import &#39;../../modules/hotel/gui/views/hotel_view.dart&#39;;<br>import &#39;app_routes.dart&#39;;<br><br>var hotelRoutes = {<br>  Routes.hotel: (context, settings) =&gt; const HotelView(),<br>  Routes.hotelDetail: (context, settings) =&gt; const HotelDetailView(),<br>};</pre><pre>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:flutter_generalization/src/modules/hotel/gui/views/home_view.dart&#39;;<br>import &#39;auth_routes.dart&#39;;<br>import &#39;hotel_routes.dart&#39;;<br><br>class Routes {<br>  static const String home = &#39;home&#39;;<br>  static const String hotel = &#39;hotel&#39;;<br>  static const String hotelDetail = &#39;hotelDetail&#39;;<br>  static const String login = &#39;login&#39;;<br>  static const String signup = &#39;signup&#39;;<br><br>  static Map&lt;String, RouteType&gt; _resolveRoutes() {<br>    return {<br>      Routes.home: (context, settings) =&gt; const HomeView(),<br>      ...authRoutes,<br>      ...hotelRoutes,<br>    };<br>  }<br><br>  static Route onGenerateRoutes(RouteSettings settings) {<br>    var routes = _resolveRoutes();<br>    try {<br>      final child = routes[settings.name];<br><br>      Widget builder(BuildContext c) =&gt; child!(c, settings);<br><br>      return MaterialPageRoute(<br>        builder: builder,<br>      );<br>    } catch (e) {<br>      throw const FormatException(&quot;--- Route doesn&#39;t exist&quot;);<br>    }<br>  }<br>}</pre><p>Then, we can refactor the Routes class by moving all the Route strings to a different file as follows :</p><pre>class AppRoutes {<br> static const String home = &#39;home&#39;;<br> static const String hotel = &#39;hotel&#39;;<br> static const String hotelDetail = &#39;hotelDetail&#39;;<br> static const String login = &#39;login&#39;;<br> static const String signup = &#39;signup&#39;;<br>}</pre><pre>class Routes {<br>  static Map&lt;String, RouteType&gt; _resolveRoutes() {<br>    return {<br>      AppRoutes.home: (context, settings) =&gt; const HomeView(),<br>      ...authRoutes,<br>      ...hotelRoutes,<br>      ...packageRoutes,<br>    };<br>  }<br><br>  static Route onGenerateRoutes(RouteSettings settings) {<br>    var routes = _resolveRoutes();<br>    try {<br>      final child = routes[settings.name];<br><br>      Widget builder(BuildContext c) =&gt; child!(c, settings);<br><br>      return MaterialPageRoute(<br>        builder: builder,<br>      );<br>    } catch (e) {<br>      throw const FormatException(&quot;--- Route doesn&#39;t exist&quot;);<br>    }<br>  }<br>}</pre><p>Then,<br>We also update the files where the routes were defined to use AppRoutes .</p><pre>import &#39;../../modules/hotel/gui/views/login_view.dart&#39;;<br>import &#39;../../modules/hotel/gui/views/signup_view.dart&#39;;<br>import &#39;app_routes.dart&#39;;<br><br>var authRoutes = {<br>  Routes.login: (context, settings) =&gt; const LoginView(),<br>  Routes.signup: (context, settings) =&gt; const SignupView(),<br>};</pre><pre>import &#39;../../modules/hotel/gui/views/hotel_detail_view.dart&#39;;<br>import &#39;../../modules/hotel/gui/views/hotel_view.dart&#39;;<br>import &#39;app_routes.dart&#39;;<br><br>var hotelRoutes = {<br>  Routes.hotel: (context, settings) =&gt; const HotelView(),<br>  Routes.hotelDetail: (context, settings) =&gt; const HotelDetailView(),<br>};</pre><p>This is good enough to be used in projects.<br>But we can take it one step further by adding types to the Map object.</p><h3># Adding Types</h3><p>First, we create a type definition as follows :</p><pre>import &#39;package:flutter/material.dart&#39;;<br>typedef RouteType = Widget Function(BuildContext, RouteSettings);</pre><p>Then, we add the return type for _resolveRoute() function as :</p><pre>  ...<br>  <br>  static Map&lt;String, RouteType&gt; _resolveRoutes() {<br>    return {<br>      AppRoutes.home: (context, settings) =&gt; const HomeView(),<br>      ...authRoutes,<br>      ...hotelRoutes,<br>    };<br>  }<br><br>...</pre><p>We should also update the type for authRoutes and hotelRoutes as follows :</p><pre>Map&lt;String, RouteType&gt; authRoutes = {<br> AppRoutes.login: (context, settings) =&gt; const LoginView(),<br> AppRoutes.signup: (context, settings) =&gt; const SignupView(),<br>};</pre><pre>Map&lt;String, RouteType&gt; hotelRoutes = {<br> AppRoutes.hotel: (context, settings) =&gt; const HotelView(),<br> AppRoutes.hotelDetail: (context, settings) =&gt; const HotelDetailView(),<br>};</pre><p>Now,<br>With this, our routes will span into multiple files which is much more manageable.</p><p>Now,<br>Let’s say we wanted to implement a new feature of packages that has routes for Package Listing View and Package Detail View . We can create a new file called package_routes.dart as :</p><pre>import &#39;package:flutter_generalization/src/core/routing/route_type.dart&#39;;<br>import &#39;package:flutter_generalization/src/modules/hotel/gui/views/package_detail_view.dart&#39;;<br>import &#39;package:flutter_generalization/src/modules/hotel/gui/views/package_listing_view.dart&#39;;<br><br>import &#39;app_routes.dart&#39;;<br>Map&lt;String, RouteType&gt; packageRoutes = {<br> AppRoutes.packageListing: (context, settings) =&gt; const PackageListingView(),<br> AppRoutes.packageDetail: (context, settings) =&gt; const PackageDetailView(),<br>};</pre><p>Then, we can add this route to the _routeResolver() as :</p><pre>...<br>  <br>  static Map&lt;String, RouteType&gt; _resolveRoutes() {<br>    return {<br>      AppRoutes.home: (context, settings) =&gt; const HomeView(),<br>      ...authRoutes,<br>      ...hotelRoutes,<br>      ...packageRoutes,<br>    };<br>  }<br><br>...</pre><p>So,<br>The next time you want to create a cohesive list of routes, you can create a new folder and create a Map&lt;String, RouteType&gt; and then import it into Routes class and spread the Map inside the return of _resolveRoutes() .</p><h3># Animating the Routes</h3><p>Let’s say that, you wanted your app route animations to be such that all the pages slide from bottom to top.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/226/1*H6YGa3WaJwSVK_hTjQSFYg.gif" /></figure><p>Achieving this is fairly easy because we have a single entry point that defines the PageRoute . This is done in Routes.onGenerateRoutes() where we’ve returned MaterialPageRoute .</p><p>The slide top-to-bottom animation can be achieved by extending PageRouteBuilder :</p><pre>class SlideRoute extends PageRouteBuilder {<br>  final Widget Function(BuildContext) builder;<br>  final Offset? startOffset;<br>  final Offset? endOffset;<br>  SlideRoute({<br>    required this.builder,<br>    bool fullscreenDialog = false,<br>    this.startOffset,<br>    this.endOffset,<br>  }) : super(<br>          fullscreenDialog: fullscreenDialog,<br>          transitionDuration: const Duration(milliseconds: 500),<br>          pageBuilder: (a, b, c) =&gt; builder(a),<br>        );<br><br>  @override<br>  Widget buildTransitions(BuildContext context, Animation&lt;double&gt; animation,<br>      Animation&lt;double&gt; secondaryAnimation, Widget child) {<br>    return SlideTransition(<br>      position: Tween&lt;Offset&gt;(<br>        begin: startOffset ?? const Offset(0, 1),<br>        end: endOffset ?? Offset.zero,<br>      ).animate(animation),<br>      child: child,<br>    );<br>  }<br>}</pre><p>Then,<br>We can replace MaterialPageRoute as :</p><pre>class Routes {<br>  static Map&lt;String, RouteType&gt; _resolveRoutes() {<br>    return {<br>      AppRoutes.home: (context, settings) =&gt; const HomeView(),<br>      ...authRoutes,<br>      ...hotelRoutes,<br>      ...packageRoutes,<br>    };<br>  }<br><br>  static Route onGenerateRoutes(RouteSettings settings) {<br>    print(settings.name);<br>    var routes = _resolveRoutes();<br>    try {<br>      final child = routes[settings.name];<br><br>      return SlideRoute(builder: (c) =&gt; child!(c, settings));<br>    } catch (e) {<br>      throw const FormatException(&quot;--- Route doesn&#39;t exist&quot;);<br>    }<br>  }<br>}</pre><p>Now,<br>All the routes in the app will be animated from bottom to top.</p><h3># Animating Specific Routes Differently</h3><p>It is possible that you might only want to animate a particular route such that it slides from bottom to top.</p><p>This can be achieved by adding an if statement in onGenerateRoutes as :</p><pre>...<br>  static Route onGenerateRoutes(RouteSettings settings) {<br>    var routes = _resolveRoutes();<br>    try {<br>      final child = routes[settings.name];<br><br>      Widget builder(BuildContext c) =&gt; child!(c, settings);<br><br>      if (settings.name == AppRoutes.hotel) {<br>        return SlideRoute(builder: builder);<br>      }<br><br>      return MaterialPageRoute(builder: builder);<br>    } catch (e) {<br>      throw const FormatException(&quot;--- Route doesn&#39;t exist&quot;);<br>    }<br>  }<br>...</pre><h3># Bonus</h3><p>The Slide Animation can be configured to slide left to right or right to left and even top to bottom .</p><p>To achieve this we just have to pass in the offset.</p><h3>Animating Left to Right</h3><pre>...<br>return SlideRoute(<br>    builder: builder,<br>    offset: const Offset(-1, 0),<br>);<br>...</pre><h3>Animating Right to Left</h3><pre>...<br>return SlideRoute(<br>    builder: builder,<br>    offset: const Offset(1, 0),<br>);<br>...</pre><h3>Animating Top to Bottom</h3><pre>...<br>return SlideRoute(<br>    builder: builder,<br>    offset: const Offset(0, -1),<br>);<br>...</pre><h3>Animating Bottom to Top</h3><pre>...<br>return SlideRoute(<br>    builder: builder,<br>    offset: const Offset(0, 1),<br>);<br>...</pre><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/managing-routes-in-flutter-like-a-pro-with-animations-9887cbe30115"><em>https://medium.com</em></a><em> on June 21, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1fefa2f966cd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building UIs without worrying about API data format.]]></title>
            <link>https://medium.com/@raralabs/building-uis-without-worrying-about-api-data-format-17fba44e168b?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/17fba44e168b</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[ui]]></category>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Wed, 21 Jun 2023 07:20:53 GMT</pubDate>
            <atom:updated>2023-06-21T07:48:27.201Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/88/0*Dz6amtrf5gT8bpjd.jpeg" /></figure><blockquote>This Article uses React Native for Demonstration but the concepts work with any UI framework or simple HTML.</blockquote><blockquote>Code can be found at the end of the article.</blockquote><p>If you’re a Frontend Developer chances are that you’ve been slowed down by waiting for the API.</p><p>What if I told you that your UI doesn’t need to depend upon whatever the API data format looks like?</p><p>Let me tell you how it’s done.<br>Let’s say you have to make a component as follows :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*gChpwHxkjXEcYapU.png" /></figure><p>If you observe this Component, you only need 5 different information:<br>1. Name<br>2. Phone Number<br>3. Gender<br>4. Age<br>5. Image</p><p>However, when backend decides to send data for this component, it might include extra information like id, address, etc.</p><p>This component doesn’t care about any other data except the described 5.<br>So, we can create a Model for those 5 data as :</p><pre>export default interface UserCardViewMOdel {<br> name: string,<br> phone: string,<br> gender: string,<br> age: string,<br> image: string,<br>}</pre><p>Then,<br>We can have the component take that data as a prop.</p><pre>const UserCard = ({data}: {data: UserCardViewModel}) =&gt; {<br> return (<br>  ...<br>   ----- Component UI defined here -----<br>  ...<br> )<br>}</pre><p>With this, we have all the data the UserCard component needs in order to render.</p><p>The required data can be accessed from the data variable as :</p><pre>const UserCard = ({data}: {data: UserCardViewModel}) =&gt; {<br> return (<br>  ...<br>        &lt;Text&gt;{data.name}&lt;/Text&gt;<br>  ...<br> )<br>}</pre><p>Now,<br>The UserCard component doesn&#39;t care about anything that happens with the API. This component is able to render the UI as long as it is given data of type UserCardViewModel .</p><p>This makes the component you write reusable across multiple projects as you can copy the component along with it’s View Model when using it. The term View Model comes from MVVM which is for Model, View, View Model .<br>A Model is a data class that holds the data of your app. A View Model is just a Model but for a View . A View is anything that appears on the screen.</p><p>Now,<br>Let’s say that the data provided by the backend looked as :</p><pre>export default interface UserResponse {<br> firstName: string,<br> lastName: string,<br> phone: string,<br> gender: string,<br> dob: string,<br> image: string,<br>}</pre><p>You’ll realize that the backend data is completely different from what you need in your UI. To make our component render we need the data to be of type UserCardViewModel .</p><p>For this, we have to convert UserResponse into UserCardViewModel .</p><p>This can be done in various ways.</p><h3># The Common Way</h3><p>One way is to convert it while passing it to the component inside the UI as :</p><pre>const HomeView = () =&gt; {<br>  let response = useGetResponseSomehow();<br>  <br>  return (<br>      ...<br>          &lt;UserCard data={{<br>            name: `${response.firstName} ${response.lastName}`,<br>            phone: response.phone,<br>            gender: response.gender,<br>            age: getAgeFromDob(response.dob),<br>            image: response.image,<br>          }} /&gt;<br>      ...<br>  )<br>}</pre><p>This will work, but, you’ve cluttered your UI code with data conversion logic.</p><p>So, a better way is to separate out this logic into an adapter.</p><h3># The Better Way</h3><p>You create an adapter as follows :</p><pre>export const UserCardAdapter = {<br> convert(response: UserResponse): UserCardViewModel {<br> return {<br> name: `${response.firstName} ${response.lastName}`,<br> phone: response.phone,<br> gender: response.gender,<br> age: getAgeFromDob(response.dob),<br> image: response.image,<br> };<br> }<br>}</pre><p>We can have the HotelCardAdapter be a Singleton.<br>Then, use it as :</p><pre>const HomeView = () =&gt; {<br>  let response = useGetResponseSomehow();<br>  <br>  return (<br>      ...<br>          &lt;UserCard data={UserCardAdapter.convert(response)} /&gt;<br>      ...<br>  )<br>}</pre><p>This is good.<br>But, this means that you need to remember to write this .convert() statement for every component.</p><p>So, we can take this adapter idea one step further by abstracting it.</p><h3># The More Better Way</h3><p>The abstraction for the adapter can be created as :</p><pre>export default interface Adapter&lt;T,D&gt; {<br> convert(data: T): D;<br>}</pre><p>Then,<br>Every adapter can implement the interface. In our UserCardAdapter example, it will become :</p><pre>export const UserCardAdapter : Adater&lt;UserResponse, UserCardViewModel&gt; = {<br> convert(response: UserResponse): UserCardViewModel {<br> return {<br> name: `${response.firstName} ${response.lastName}`,<br> phone: response.phone,<br> gender: response.gender,<br> age: getAgeFromDob(response.dob),<br> image: response.image,<br> };<br> }<br>}</pre><p>Now,<br>We can create an adapter component that will call the .convert() as :</p><pre>const AdaptedComponent = &lt;T,D&gt; ({<br> data,<br> adapter,<br> children,<br>}: {<br> data: T,<br> adapter: Adapter&lt;T,D&gt;<br> children: (data: D) =&gt; JSX.Element<br>}) =&gt; {<br> const adaptedData = adapter.convert(data);<br> return children(adaptedData);<br>}<br> <br>export default AdaptedComponent;</pre><p>Then, this can be used as :</p><pre>const HomeView = () =&gt; {<br>  let response = useGetResponseSomehow();<br>  <br>  return (<br>      ...<br>          &lt;AdaptedComponent&lt;UserResponse, UserCardViewModel&gt;<br>              adapter={UserCardAdapter}<br>              data={response}<br>          &gt;<br>          {<br>            data =&gt; &lt;UserCard data={UserCardAdapter.convert(response)} /&gt;<br>          }<br>          &lt;/AdaptedComponent&gt;<br>      ...<br>  )<br>}</pre><p>Why is this good even though clearly, I seem to have to write more code than remembering to use .convert() ?</p><p>It’s good because we have abstracted the logic of .convert() and all we need to do is wrap our component with &lt;AdaptedComponent /&gt; .</p><p>With this, we’ll not have to worry about our API’s data format.<br>All we need to do is create a Model for our GUI and then adapt the data from API into the GUI.</p><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/building-uis-without-worrying-about-api-data-format-c0b6a3441c6e"><em>https://medium.com</em></a><em> on June 21, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=17fba44e168b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Polymorphism in depth.]]></title>
            <link>https://medium.com/@raralabs/polymorphism-in-depth-219a50eadea9?source=rss-2c1ad3865b8e------2</link>
            <guid isPermaLink="false">https://medium.com/p/219a50eadea9</guid>
            <dc:creator><![CDATA[Rara Labs]]></dc:creator>
            <pubDate>Thu, 13 Apr 2023 05:59:52 GMT</pubDate>
            <atom:updated>2023-04-13T06:00:35.504Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/974/0*DSkTRJSw1KOv1i4f" /></figure><blockquote><em>Article assumes user to have some experience in Object Oriented Programming.<br>The article uses Dart as a programming language for example.</em></blockquote><p>To understand polymorphism, let’s take an example of a Laptop.</p><p>Suppose you have a laptop with USB ports.<br>What are the things that you can insert into the USB ports ?<br>The answer is anything that has a USB male connector.</p><p>Can it be a mouse ? YES.<br>Can it be a keyboard ? YES.<br>Can it be a printer ? YES.</p><p>What’s common in these devices ?<br>They are I/O devices.<br>They are used for input and output.</p><p>Now, programmers realized very quickly that it was of our interest to have our systems (computers) be independent of I/O devices.</p><p>Creation of this independence is called <strong>polymorphism</strong>.</p><p>In our example USB was an interface between our laptop and the I/O devices.</p><p>Now, how do we do that in code ?</p><p>Consider the following example :</p><h3>Problem</h3><p>Let’s say that module A calls module B . <br>We can consider module A to be our laptop and module B to be our keyboard.</p><p>In this scenario, the code will look as follows :</p><p>Here, <br>ModuleA knows about ModuleB because the code in module_a.dart explicity has a declaration of ModuleB in relative line number 11.</p><p>The following will be the dependency graph :</p><p>This means ModuleA is tightly coupled to ModuleB in compile time.<br>So, if ModuleA has to compile then it must have ModuleB compiled too. That&#39;s bad.</p><p>Why’s this bad ?<br>Back in our example, this means that when our laptop was made it will only support a particular kind of keyboard designed for that laptop but we want any keyboard to work with our system.</p><h3>Solution</h3><p>To solve this, we create an interface between ModuleA and ModuleB so that ModuleA doesn&#39;t need to directly reference ModuleB . This can be done because ModuleA depends upon the function doSomething() present in ModuleB .</p><p>Now, <br>In module_a.dart there is no declaration of ModuleB directly, instead it has a reference to some Interface .</p><p>Now, the dependency graph is like :</p><p>If we look at interface.dart we&#39;ll see that there is no implementation of the method doSomething() but ModuleA is able to call the function doSomething() in relative line number 4, in module_a.dart .</p><p>Notice, that ModuleB is no longer referenced in ModuleA anywhere in compile time but we can inject ModuleB into ModuleA at runtime via the constructor.</p><p>Why is this good ? <br>It’s good because, this means that now we can have a ModuleX that implements the Interface which can work with ModuleA .</p><p>This gives the developer immense power.</p><p>This means that we can change the behavior of ModuleA without ever changing it&#39;s source code.</p><p>We can inject the dependencies as follows :</p><p>Here, <br>ModuleA accepts ModuleX as well as ModuleB .</p><p>But, when we look at the output :</p><p>The behavior of ModuleA is different for the same function call.</p><p>This is why we like Polymorphism.<br>It gives the developer immense power.</p><p><em>Originally published at </em><a href="https://medium.com/@sushaanshakya88/polymorphism-in-depth-13549b3f5de"><em>https://medium.com</em></a><em> on April 13, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=219a50eadea9" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>