<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Edo Lubis</title>
    <description>The latest articles on Forem by Edo Lubis (@edolubis21).</description>
    <link>https://forem.com/edolubis21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1391235%2F9d4eb90e-98f3-4ac1-8cde-ca98cce344be.jpeg</url>
      <title>Forem: Edo Lubis</title>
      <link>https://forem.com/edolubis21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/edolubis21"/>
    <language>en</language>
    <item>
      <title>Modularization in Flutter App</title>
      <dc:creator>Edo Lubis</dc:creator>
      <pubDate>Mon, 16 Dec 2024 09:07:57 +0000</pubDate>
      <link>https://forem.com/tentanganak/modularization-in-flutter-app-bb6</link>
      <guid>https://forem.com/tentanganak/modularization-in-flutter-app-bb6</guid>
      <description>&lt;p&gt;As the complexity of a project increases, so does the amount of code we write. Therefore, maintaining code quality, scalability, and readability becomes even more important. One approach often used by engineers to address these challenges is modularization. &lt;/p&gt;

&lt;p&gt;What is modularization? Simply put, modularization is the process of breaking down a problem into smaller, more specific tasks or components, so that each part has a clear and focused purpose.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7v0kvubl6qwjs2y7d93i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7v0kvubl6qwjs2y7d93i.png" alt="Modularization example" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However keep in mind, modularization doesn’t necessarily reduce the complexity of your project. The overall complexity may remain the same, but modularization makes it easier to manage and maintain.&lt;/p&gt;

&lt;p&gt;We can separate modules based on features, by layers (such as UI, model, etc.), or a combination of both, depending on our needs and preferences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlanja5tr2p9s5h1y6t9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlanja5tr2p9s5h1y6t9.png" alt="Module based on" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When modularizing, it's important to remember that good modularization is characterized by high cohesion, low coupling and bounded context. &lt;/p&gt;

&lt;p&gt;High cohesion means that each module has a specific function and focuses on a single purpose. For example, a module responsible for user authentication should only handle tasks like verifying user credentials or managing login sessions. It should not include unrelated functions, such as processing payments or managing user preferences.&lt;/p&gt;

&lt;p&gt;Low coupling means that modules do not rely excessively on other modules, so changes in one module do not have a significant impact on other modules. For example, the user authentication module should be designed in such a way that it operates independently of other modules.&lt;/p&gt;

&lt;p&gt;Bounded context means that each module should operate within its own domain, with its own set of rules and responsibilities. It ensures that the boundaries of a module are clearly defined, preventing its domain from spilling over into others. as an analogy, imagine you are designing an amusement park with various rides. Each ride in the park has its own theme and different rules, such as the roller coaster, haunted house, or giant Ferris wheel. Each ride has clear boundaries, with features and rules that apply only within that ride.&lt;/p&gt;

&lt;p&gt;In the roller coaster, the main rule is speed and passenger safety. All design and interactions in the roller coaster are focused on providing a thrilling and safe high-speed experience.&lt;/p&gt;

&lt;p&gt;In the haunted house, the main rule is to create suspense and surprise. All design elements and interactions in the haunted house focus on creating a spooky and mysterious atmosphere.&lt;/p&gt;

&lt;p&gt;In the giant Ferris wheel, the main rule is to enjoy the view. This ride is designed to offer a calm and scenic experience, without speed or tension.&lt;/p&gt;

&lt;p&gt;Even though all these rides are part of the same amusement park, each operates within its own bounded context, with its own rules, definition and goals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv78growiy43ntugexlkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv78growiy43ntugexlkm.png" alt="a1" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.geeksforgeeks.org/software-engineering-coupling-and-cohesion/" rel="noopener noreferrer"&gt;learn more about cohession and coupling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets take the example of an ecommerce application where a user can Login, Browse a list of products, Add products to the cart, Make a payment and chose the shipping method. &lt;/p&gt;

&lt;p&gt;This application can be modularized as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Module: Handles user authentication (login, signup, password reset).&lt;/li&gt;
&lt;li&gt;Product Module: Handles product list, product details, and product filtering/searching.&lt;/li&gt;
&lt;li&gt;Cart Module: Manages the shopping cart, including adding/removing items, displaying total price, and updating quantities.&lt;/li&gt;
&lt;li&gt;Payment Module: Processes payments and manages transactions.&lt;/li&gt;
&lt;li&gt;Shipping Module: Manages shipping options and delivery methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And make sure, each module has boundaries defined based on the principle of bounded context, which means that each domain should have its own rules and responsibilities, ensuring that it operates independently within its own scope. This approach guarantees that each module has high cohesion (meaning all related functionalities are grouped together) and low coupling (meaning minimal dependencies on other modules).&lt;/p&gt;

&lt;h2&gt;
  
  
  Modular Approach in flutter using flutter package
&lt;/h2&gt;

&lt;p&gt;In Flutter, we can adopt a modular approach using Flutter packages. For example, if we create a new application named "TradingApp," we can organize the modules based on features such as Wallet, Login, and Market.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new Flutter project.&lt;/li&gt;
&lt;li&gt;After creating the TradingApp project, create the login module by going to File → New → New Flutter Project, and select Package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy94b1ca8a9onynxyxujc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy94b1ca8a9onynxyxujc.png" alt="a2" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnkialx1mtwrmmoyo3so.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnkialx1mtwrmmoyo3so.png" alt="a3" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then, create the other modules. The project structure will look like this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wxqq3o08l5sjlb2x4zi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wxqq3o08l5sjlb2x4zi.png" alt="a4" width="776" height="1016"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To enable TradingApp to use the other modules, we need to link them with import dependencies, similar to Flutter libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03ujmzhzmj6dum0g7ppi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03ujmzhzmj6dum0g7ppi.png" alt="a5" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This way, we can use different modules within our application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g55heimiowwbiut154u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g55heimiowwbiut154u.png" alt="a6" width="800" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can also group modules into a single folder in our project. This will make the project structure and pubspec.yaml file look like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42gv5o49k7son1greujl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42gv5o49k7son1greujl.png" alt="a7" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Or If we prefer not to keep the modules in the same repository, we can use a different approach by placing each module in separate Git repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvzppr4b10q0p9977tjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvzppr4b10q0p9977tjd.png" alt="a8" width="800" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclussion
&lt;/h2&gt;

&lt;p&gt;Modularization enhances software development by breaking complex projects into manageable, focused components, which improves code quality, scalability, and maintainability. This approach allows for better organization, easier updates, and reusability of code, and facilitates collaboration among team members. And, for modularization to be effective, it is crucial to ensure high cohesion within modules and maintain low coupling between them.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>5 Tips to Improve Your Flutter Performance</title>
      <dc:creator>Edo Lubis</dc:creator>
      <pubDate>Mon, 01 Jul 2024 06:15:18 +0000</pubDate>
      <link>https://forem.com/tentanganak/5-tips-to-improve-your-flutter-performance-2279</link>
      <guid>https://forem.com/tentanganak/5-tips-to-improve-your-flutter-performance-2279</guid>
      <description>&lt;p&gt;When developing mobile applications using Flutter, performance is crucial. A smoothly running application provides a better user experience, allowing users to explore the app without feeling annoyed or frustrated by slow startup times, crashes, or jank.&lt;/p&gt;

&lt;p&gt;Optimizing application performance includes various aspects, such as app start times and efficient memory management. By minimizing the workload during initial launch, using efficient state management, and properly disposing of resources, developers can ensure a smoother user experience. Here are some ways to improve the performance of Flutter applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Avoid Unnecessary Initialization in the Main App&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;main()&lt;/code&gt; function is the entry point of a Flutter application. Keeping it clean and avoiding unnecessary initializations here is vital. Heavy  operations should be deferred until they are needed, preferably in the appropriate widgets or services. And minimize the use of asynchronous functions in the main() function to ensure the first render time is as fast as possible. This helps to speed up the app's startup time, providing users with a quicker load time.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Additionally, we can also speed up the process by using Future.wait to perform multiple asynchronous operations concurrently. This technique allows us to initiate several tasks simultaneously and wait for all of them to complete, thereby optimizing the overall initialization time and enhancing the app's performance right from the start&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

void main() async {
  await Future.wait([initFirebase(), initDatabase()]);
  runApp(const MyApp());
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And consider using a splash screen as the initial page to wait for the initiation to be completed.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

void main() async {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State&amp;lt;MyApp&amp;gt; createState() =&amp;gt; _MyAppState();
}

class _MyAppState extends State&amp;lt;MyApp&amp;gt; {
  bool isSplashShow = true;

  @override
  void initState() {
    super.initState();
    _init();
  }

  void _init() async {
    await Future.wait([_initFirebase(), _initDatabase()]);
    _checkIsLoggedIn();

    isSplashShow = false;
  }

  Future&amp;lt;void&amp;gt; _initFirebase() async {}

  Future&amp;lt;void&amp;gt; _initDatabase() async {}

  void _checkIsLoggedIn() {}

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: isSplashShow
          ? const SplashPage()
          : const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Prefer Using &lt;code&gt;ListView&lt;/code&gt; or &lt;code&gt;CustomScrollView&lt;/code&gt; Over &lt;code&gt;SingleChildScrollView&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
When dealing with scrollable content, using &lt;code&gt;ListView&lt;/code&gt; or &lt;code&gt;CustomScrollView&lt;/code&gt; is more performance-efficient compared to &lt;code&gt;SingleChildScrollView&lt;/code&gt;. &lt;code&gt;ListView&lt;/code&gt; and &lt;code&gt;CustomScrollView&lt;/code&gt; are optimized for scrolling performance and memory usage, especially with large datasets, as they lazily build and dispose of widgets as they come into and out of the viewport.&lt;/p&gt;

&lt;p&gt;For example, an application that displays a list using the SingleChildScrollView widget to show text from 1-999 uses approximately 30-40 MB of memory.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final List&amp;lt;String&amp;gt; items = [];

  @override
  void initState() {
    super.initState();
    _init();
  }

  void _init() {
    for (int i = 0; i &amp;lt; 9999; i++) {
      items.add(i.toString());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Title"),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            for (final item in items)
              SizedBox(
                width: double.infinity,
                child: Text(item),
              ),
          ],
        ),
      ),
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1u3gt2m8075dad2qcq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1u3gt2m8075dad2qcq6.png" alt="the memory usage around is 30-40 MB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, if the ListView widget is used to display text from 1-999, the memory usage is around 7-10 MB and also improve frame rendering time.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final List&amp;lt;String&amp;gt; items = [];

  @override
  void initState() {
    super.initState();
    _init();
  }

  void _init() {
    for (int i = 0; i &amp;lt; 9999; i++) {
      items.add(i.toString());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Title"),
      ),
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return Text(items[index]);
        },
      ),
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4gnktdnfg661vl7cy01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4gnktdnfg661vl7cy01.png" alt="the memory usage is around 7-10 MB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use &lt;code&gt;cacheWidth&lt;/code&gt; and &lt;code&gt;cacheHeight&lt;/code&gt; in &lt;code&gt;Image&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Displaying images in a Flutter application is a basic and easy task. However, many of us are unaware that displaying images whose sizes do not match what we want to display in a widget will cause our application to use unnecessary memory.&lt;/p&gt;

&lt;p&gt;Flutter provides a way to detect oversized images by using debugInvertOversizedImages = true. This can alert developers if the displayed images are larger than desired.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

void main() {
  debugInvertOversizedImages = true;
  return runApp(const MyApp());
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("counter"),
      ),
      body: Column(
        children: [
          Image.network(
            "https://images.unsplash.com/photo-1715196372160-31ba56b1a2f9",
          ),
        ],
      ),
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the image dimensions exceed what is suitable for the widget, Flutter will generate an error when debugInvertOversizedImages is set to true.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq5pu5uaoyiq9u8k3f7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq5pu5uaoyiq9u8k3f7m.png" alt="warning error memory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this issue, we can use the cacheWidth and cacheHeight parameters in the Image widget. These parameters allow us to control memory usage by resizing the displayed image. Let's see how we can utilize them:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    Image.network(
            "https://images.unsplash.com/photo-1690906379371-9513895a2615",
            height: 300,
            width: 200,
            cacheHeight: 300,
            cacheWidth: 200,
          ),


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By setting cacheWidth and cacheHeight to appropriate values, we can ensure that the image is displayed with the desired dimensions without unnecessarily consuming memory.&lt;/p&gt;

&lt;p&gt;However, this solution is not perfect because each device has a different pixel ratio. Errors may not appear on our debug device but might on other devices with different pixel ratios.&lt;/p&gt;

&lt;p&gt;Therefore, we can calculate cacheHeight and cacheWidth by multiplying with MediaQuery.of(context).devicePixelRatio.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

extension ImageExtension on num {  
  int cacheSize(BuildContext context) {  
    return (this * MediaQuery.of(context).devicePixelRatio).round();  
  }  
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That extension allows us to easily calculate the cache size for our images and make the optimization process smoother:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    debugInvertOversizedImages = false;
    return Scaffold(
      appBar: AppBar(
        title: const Text("counter"),
      ),
      body: Column(
        children: [
          Image.network(
            "https://images.unsplash.com/photo-1690906379371-9513895a2615",
            height: 300,
            width: 200,
            cacheHeight: 300.cacheSize(context),
            cacheWidth: 200.cacheSize(context),
          ),
        ],
      ),
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Dispose Unused Streams and Controller&lt;/strong&gt;&lt;br&gt;
When streams and controllers are no longer needed, failing to dispose of them can cause memory leaks. Memory leaks occur when memory that is no longer needed is not released, which over time can consume a significant portion of available memory, leading to poor performance and even application crash (out of memory).&lt;/p&gt;

&lt;p&gt;To prevent memory leaks, it is important to dispose streams and controllers when they are no longer needed. This is typically done in the &lt;code&gt;dispose&lt;/code&gt; method of a &lt;code&gt;StatefulWidget&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  late StreamSubscription _subscription;
  late TextEditingController _textEditingController;
  late ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _subscription = counterBloc.counterStream.listen((data) {
      // handle data
    });
    _textEditingController = TextEditingController();
    _scrollController = ScrollController();
    _textEditingController.addListener(() {
      // handle data
    });
    _scrollController.addListener(() {
      // handle data
    });
  }

  @override
  void dispose() {
    _subscription.cancel();
    _textEditingController.dispose();
    _scrollController.dispose();
    super.dispose();
  }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Use &lt;code&gt;const&lt;/code&gt; and Prefer State Management Solutions&lt;/strong&gt;&lt;br&gt;
Using &lt;code&gt;const&lt;/code&gt; constructors for widgets whenever possible helps Flutter optimize the build process by reusing widgets rather than recreating them. Additionally, employing state management solutions like BLoC, Riverpod, or Provider can lead to better-organized code and more efficient state handling, ultimately improving performance.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final counterBloc = CounterBloc();

final counterProvider =
    StateNotifierProvider.autoDispose&amp;lt;CounterController, int&amp;gt;(
  (ref) =&amp;gt; CounterController(),
);

class CounterController extends StateNotifier&amp;lt;int&amp;gt; {
  CounterController() : super(0);

  void increment() {
    state = state + 1;
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("counter"),
      ),
      body: Column(
        children: const [
          IncrementWidget(),
          CounterWidget(),
        ],
      ),
    );
  }
}

class IncrementWidget extends ConsumerWidget {
  const IncrementWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, ref) {
    return ElevatedButton(
      onPressed: () {
        ref.read(counterProvider.notifier).increment();
      },
      child: const Text("counter"),
    );
  }
}

class CounterWidget extends ConsumerWidget {
  const CounterWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Data persistence in flutter</title>
      <dc:creator>Edo Lubis</dc:creator>
      <pubDate>Mon, 03 Jun 2024 11:19:52 +0000</pubDate>
      <link>https://forem.com/tentanganak/data-persistence-in-flutter-3902</link>
      <guid>https://forem.com/tentanganak/data-persistence-in-flutter-3902</guid>
      <description>&lt;p&gt;When you develop an application, usually, the data used to display information originates from the internet. However, there are scenarios where the data from the internet needs to be stored somewhere on your local device. This can be for functionality purposes or to enhance performance and user experience while using the application.&lt;/p&gt;

&lt;p&gt;Take the Facebook application as an example: when you open the main page or page detail, then close the application and reopen it, you will find yourself on the last visited page, and the page immediately displays data as if there were no loading process. This can happen because the data is stored locally, and when the application is opened, it uses the stored data to display the main or page detail while loading data from the internet to get the latest information.&lt;/p&gt;

&lt;p&gt;In Flutter itself, there are many libraries designed to store data on the local device. In this article, I will share How to persist data using the best libraries (in my opinion) for storing data locally in Flutter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. shared_preferences&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://pub.dev/packages/shared_preferences"&gt;shared_preferences&lt;/a&gt; is a plugin for reading and writing simple key-value pairs. It uses NSUserDefaults on iOS and SharedPreferences on Android. shared_preferences is a best choice for storing small and non-sensitive data, such as theme preferences, language settings, settings.&lt;/p&gt;

&lt;p&gt;implementation example:&lt;br&gt;
write data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; saveTheme(bool isLightMode) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setBool('isLightMode', isLightMode);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;bool?&amp;gt; readTheme() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final bool? isLightMode = prefs.getBool('isLightMode');
    return isLightMode;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; deleteTheme() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.remove('isLightMode');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SharedPreferences is limited to only supporting certain data types like int, double, bool, String, and List. However, we can store objects in SharedPreferences by converting them into a string. One common way to do this is by using JSON. We can convert an object to a JSON string when saving it and then convert it back to an object when retrieving it.&lt;/p&gt;

&lt;p&gt;implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Setting {
  bool? isLightMode;
  String? lang;
  int? counter;

  Setting({this.isLightMode, this.lang, this.counter});

  Setting.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    isLightMode = json['isLightMode'];
    lang = json['lang'];
    counter = json['counter'];
  }

  Map&amp;lt;String, dynamic&amp;gt; toJson() {
    final Map&amp;lt;String, dynamic&amp;gt; data = &amp;lt;String, dynamic&amp;gt;{};
    data['isLightMode'] = isLightMode;
    data['lang'] = lang;
    data['counter'] = counter;
    return data;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Future&amp;lt;void&amp;gt; saveSetting(Setting setting) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('setting', jsonEncode(setting.toJson()));
  }

  Future&amp;lt;Setting?&amp;gt; readSetting() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String? json = prefs.getString('setting');
    if (json == null) return null;
    return Setting.fromJson(jsonDecode(json));
  }

  Future&amp;lt;void&amp;gt; deleteSetting() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.remove('setting');
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. sqflite&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://pub.dev/packages/sqflite"&gt;sqflite&lt;/a&gt; is a Flutter plugin that provides access to SQLite, a lightweight, serverless relational database commonly used in mobile applications for local data storage. Sqflite is a popular choice for applications requiring local data storage with more complex and relational structures.&lt;br&gt;
implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Item {
  final int id;
  final String title;
  final String description;

  const Item({
    required this.title,
    required this.description,
    required this.id,
  });

  factory Item.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt; Item(
        id: json['id'],
        title: json['title'],
        description: json['description'],
      );

  Map&amp;lt;String, dynamic&amp;gt; toJson() =&amp;gt; {
        'id': id,
        'title': title,
        'description': description,
      };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class DatabaseHelper {
  static const int _version = 1;
  static const String _dbName = "items.db";

  static Future&amp;lt;Database&amp;gt; _getDB() async {
    return openDatabase(
      join(await getDatabasesPath(), _dbName),
      onCreate: (db, version) async =&amp;gt; await db.execute(
          "CREATE TABLE Item(id INTEGER PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL);"),
      version: _version,
    );
  }

  static Future&amp;lt;int&amp;gt; addItem(Item item) async {
    final db = await _getDB();
    return await db.insert("Item", item.toJson(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  static Future&amp;lt;int&amp;gt; updateItem(Item item) async {
    final db = await _getDB();
    return await db.update(
      "Item",
      item.toJson(),
      where: 'id = ?',
      whereArgs: [item.id],
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  static Future&amp;lt;int&amp;gt; deleteItem(Item item) async {
    final db = await _getDB();
    return await db.delete(
      "Item",
      where: 'id = ?',
      whereArgs: [item.id],
    );
  }

  static Future&amp;lt;List&amp;lt;Item&amp;gt;?&amp;gt; getAllItems() async {
    final db = await _getDB();
    final List&amp;lt;Map&amp;lt;String, dynamic&amp;gt;&amp;gt; maps = await db.query("Item");
    if (maps.isEmpty) return null;
    return List.generate(maps.length, (index) =&amp;gt; Item.fromJson(maps[index]));
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. hive&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://pub.dev/packages/hive"&gt;Hive&lt;/a&gt; is a key-value database written in Dart. Hive excels in write and delete operations compared to SQLite and SharedPreferences, and it is on par with SharedPreferences in read performance, while SQLite is significantly slower. Hive has built-in encryption using AES-256, making it suitable for storing sensitive data.&lt;br&gt;
implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; saveUser(Map&amp;lt;String, dynamic&amp;gt; user) async {
  var box = await Hive.openBox('myBox');
  await box.put('user', user);
}

Future&amp;lt;Map&amp;lt;String, dynamic&amp;gt;?&amp;gt; readUser() async {
  var box = await Hive.openBox('myBox');
  final Map&amp;lt;String, dynamic&amp;gt;? json = box.get('user');
  return json;
}

Future&amp;lt;void&amp;gt; deleteUser() async {
  var box = await Hive.openBox('myBox');
  await box.delete('user');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your project uses the &lt;a href="https://pub.dev/packages/flutter_bloc"&gt;flutter_bloc&lt;/a&gt; library for state management, you can easily cache data locally using Hive. The creator of flutter_bloc has developed the &lt;a href="https://pub.dev/packages/hydrated_bloc"&gt;hydrated_bloc&lt;/a&gt; library, which includes HydratedStorage that comes with Hive implemented.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorage.webStorageDirectory
        : await getTemporaryDirectory(),
  );
  runApp(const MyApp());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserCubit extends HydratedCubit&amp;lt;User?&amp;gt; {
  UserCubit() : super(null);

  void login(User user) =&amp;gt; emit(user);

  @override
  User? fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      json['user'] != null ? User.fromJson(json['user']) : null;

  @override
  Map&amp;lt;String, dynamic&amp;gt; toJson(User? state) =&amp;gt; {'user': state};
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. isar&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://pub.dev/packages/isar"&gt;Isar&lt;/a&gt; was created by the creator of Hive to overcome some limitations present in Hive and to offer a more advanced and efficient solution for local data storage in Flutter and Dart applications.&lt;/p&gt;

&lt;p&gt;Isar is a fast, cross-platform NoSQL database that is well-integrated with Flutter and Dart. It is designed to provide high performance and ease of use.&lt;br&gt;
implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  isar: ^2.5.0
  isar_flutter_libs: ^2.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^2.2.0
  isar_generator: ^2.5.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;part 'isar_page.g.dart';

@Collection()
class Post {
  int id = Isar.autoIncrement;

  late String title;

  late DateTime date;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;run  flutter pub run build_runner&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;late Isar isar;

@override
void initState() {
  super.initState();
  openInstance();
}

void openInstance() async {
  final dir = await getApplicationSupportDirectory();
  isar = await Isar.open(
    schemas: [PostSchema],
    directory: dir.path,
    inspector: true,
  );
}

void readPost() async {
  final posts = await isar.posts
      .filter()
      .titleContains('awesome', caseSensitive: false)
      .sortByDateDesc()
      .limit(10)
      .findAll();
  print(posts);
}

void createPost() async {
  final newPost = Post()
    ..title = 'awesome new database'
    ..date = DateTime.now();

  await isar.writeTxn((isar) async {
    newPost.id = await isar.posts.put(newPost);
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. flutter_secure_storage&lt;/strong&gt;&lt;br&gt;
If you want to store sensitive data, then &lt;a href="https://pub.dev/packages/flutter_secure_storage"&gt;flutter_secure_storage&lt;/a&gt; is one of the best solutions. flutter_secure_storage is a Flutter package that provides a secure way to store sensitive data on the device, using Keychain on iOS and a KeyStore based solution on Android.&lt;br&gt;
implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Auth {
  String? token;
  String? id;

  Auth({this.token, this.id});

  Auth.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    token = json['token'];
    id = json['id'];
  }

  Map&amp;lt;String, dynamic&amp;gt; toJson() {
    final Map&amp;lt;String, dynamic&amp;gt; data = &amp;lt;String, dynamic&amp;gt;{};
    data['token'] = token;
    data['id'] = id;
    return data;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  final storage = const FlutterSecureStorage();

  Future&amp;lt;void&amp;gt; saveAuth(Auth auth) async {
    await storage.write(key: 'auth', value: jsonEncode(auth.toJson()));
  }

  Future&amp;lt;Auth?&amp;gt; readAuth() async {
    final String? json = await storage.read(key: 'auth');
    if (json == null) return null;
    return Auth.fromJson(jsonDecode(json));
  }

  Future&amp;lt;void&amp;gt; deleteAuth() async {
    await storage.delete(key: 'auth');
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. get_storage&lt;/strong&gt;&lt;br&gt;
If you're using GetX as state management, then you might be familiar with &lt;a href="https://pub.dev/packages/get_storage"&gt;get_storage&lt;/a&gt;. get_storage is a local data storage package developed by the GetX team for Flutter. It's a lightweight and easy-to-use storage solution, allowing you to persistently store data in key-value pairs on the user's device.&lt;br&gt;
implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class User {
  String? name;
  String? id;

  User({this.name, this.id});

  User.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    name = json['name'];
    id = json['id'];
  }

  Map&amp;lt;String, dynamic&amp;gt; toJson() {
    final Map&amp;lt;String, dynamic&amp;gt; data = &amp;lt;String, dynamic&amp;gt;{};
    data['name'] = name;
    data['id'] = id;
    return data;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final box = GetStorage();

Future&amp;lt;void&amp;gt; saveUser(User user) async {
  await box.write('user', jsonEncode(user.toJson()));
}

User? readUser() {
  final String? json = box.read('user');
  if (json == null) return null;
  return User.fromJson(jsonDecode(json));
}

Future&amp;lt;void&amp;gt; deleteUser() async {
  await box.remove('user');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;get_storage is well-suited for applications with simple and fast data storage needs. However, for more complex requirements such as indexing or ensuring disk write confirmation, it's recommended to consider using sqflite, Hive, isar, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
There are the best libraries for persist data locally, The usage of the library typically depends on the type of data being stored, whether it is sensitive data, data with relationships, or just simple API responses.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Creating your own packages and plugins in flutter</title>
      <dc:creator>Edo Lubis</dc:creator>
      <pubDate>Mon, 01 Apr 2024 03:45:48 +0000</pubDate>
      <link>https://forem.com/tentanganak/creating-your-own-packages-and-plugins-in-flutter-dh6</link>
      <guid>https://forem.com/tentanganak/creating-your-own-packages-and-plugins-in-flutter-dh6</guid>
      <description>&lt;p&gt;When building applications with Flutter, Developers often rely on packages/plugins from &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; for their projects, whether developed by Google's team or by others. Utilizing these packages/plugins is highly beneficial as it can faster the development process, saving developers time by avoiding the need to create everything from scratch.&lt;/p&gt;

&lt;p&gt;but consider this scenario…&lt;/p&gt;

&lt;p&gt;Imagine you have developed a fantastic widget that enhances the functionality of your application. Wouldn't it be better to use that code in other of your projects without rewriting it? &lt;/p&gt;

&lt;p&gt;or Imagine, where the package/plugin you heavily rely on becomes discontinued or unsupported. You're left in a dilemma, uncertain about how to proceed with your project.&lt;/p&gt;

&lt;p&gt;In these scenario, the solution is create your own Flutter packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Packages
&lt;/h2&gt;

&lt;p&gt;Let's illustrate this with an example. Suppose we want to create a package to show a beautiful  modal and we call the package name "beautiful_modal".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Create a new Flutter project using Android Studio and select the project type as a package.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqdwn1tuue7ji5x84f2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqdwn1tuue7ji5x84f2k.png" alt="Create a new Flutter project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; After the project is successfully created, the folder structure will look like this.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f3bdscbakpmjnjdchua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f3bdscbakpmjnjdchua.png" alt="folder structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Code your package according to your needs. For example, for the "beautiful_modal" package, the code will look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

library beautiful_modal;

import 'package:flutter/material.dart';

void showBeautifulDialog(BuildContext context, String title, String subtitle) {
  showDialog(
    context: context,
    builder: (BuildContext context) {
      return BeautifulModal(
        subTitle: subtitle,
        title: title,
      );
    },
  );
}

class BeautifulModal extends StatelessWidget {
  final String title;
  final String subTitle;

  const BeautifulModal({Key? key, required this.title, required this.subTitle})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16.0),
      ),
      elevation: 0,
      backgroundColor: Colors.transparent,
      child: contentBox(context),
    );
  }

  Widget contentBox(BuildContext context) {
    return Stack(
      children: &amp;lt;Widget&amp;gt;[
        Container(
          padding:
              const EdgeInsets.only(left: 20, top: 50, right: 20, bottom: 20),
          margin: const EdgeInsets.only(top: 20),
          decoration: BoxDecoration(
            shape: BoxShape.rectangle,
            color: Colors.white,
            borderRadius: BorderRadius.circular(16),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: &amp;lt;Widget&amp;gt;[
              const SizedBox(
                height: 100,
                width: 100,
                child: FlutterLogo(),
              ),
              const SizedBox(height: 8),
              Text(
                title,
                style:
                    const TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
              ),
              const SizedBox(height: 8),
              Text(
                subTitle,
                style: const TextStyle(fontSize: 14),
              ),
              const SizedBox(height: 8),
              Align(
                alignment: Alignment.bottomRight,
                child: ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: const Text(
                    'Close',
                    style: TextStyle(fontSize: 18),
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Run and test your package.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73xvnxreqyh4qas7yniy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73xvnxreqyh4qas7yniy.png" alt="Test your package"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Plugins
&lt;/h2&gt;

&lt;p&gt;You might wonder what a plugin is and how it differs from a package. Simply put, a plugin is a type of package designed to act as a bridge between Flutter's Dart code and platform-specific APIs on native operating systems like iOS and Android. So, all plugins are packages, but not all packages are plugins.&lt;/p&gt;

&lt;p&gt;For example, let's say we want to show a native modal from iOS or Android.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxljobkk5b3bvb5j50ld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxljobkk5b3bvb5j50ld.png" alt="Native Modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The modal above is not created using Flutter code; instead, it's the default modal provided by the operating system (OS).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Create a new Flutter project using Android Studio and select the project type as a plugin.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuifk0fb4nb4q31yyizc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuifk0fb4nb4q31yyizc3.png" alt="Create a new Flutter project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; After the project is successfully created, the folder structure will look like this.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20grz1buffybbymyqt4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20grz1buffybbymyqt4m.png" alt="folder structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Dart Code
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Open the &lt;code&gt;beautiful_native_modal_plaform_interface.dart&lt;/code&gt; file and replace the &lt;code&gt;getPlatformVersion&lt;/code&gt; function with &lt;code&gt;showNativeModal&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import 'package:plugin_platform_interface/plugin_platform_interface.dart';

import 'beautiful_native_modal_method_channel.dart';

abstract class BeautifulNativeModalPlatform extends PlatformInterface {
  /// Constructs a BeautifulNativeModalPlatform.
  BeautifulNativeModalPlatform() : super(token: _token);

  static final Object _token = Object();

  static BeautifulNativeModalPlatform _instance = MethodChannelBeautifulNativeModal();

  /// The default instance of [BeautifulNativeModalPlatform] to use.
  ///
  /// Defaults to [MethodChannelBeautifulNativeModal].
  static BeautifulNativeModalPlatform get instance =&amp;gt; _instance;

  /// Platform-specific implementations should set this with their own
  /// platform-specific class that extends [BeautifulNativeModalPlatform] when
  /// they register themselves.
  static set instance(BeautifulNativeModalPlatform instance) {
    PlatformInterface.verifyToken(instance, _token);
    _instance = instance;
  }

  Future&amp;lt;void&amp;gt; showNativeModal(String message) {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Open the beautiful_native_modal_method_channel.dart file and implement the showNativeModal function.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'beautiful_native_modal_platform_interface.dart';

/// An implementation of [BeautifulNativeModalPlatform] that uses method channels.
class MethodChannelBeautifulNativeModal extends BeautifulNativeModalPlatform {
  /// The method channel used to interact with the native platform.
  @visibleForTesting
  final methodChannel = const MethodChannel('beautiful_native_modal');

  @override
  Future&amp;lt;void&amp;gt; showNativeModal(String message) async {
    await methodChannel.invokeMethod&amp;lt;String&amp;gt;('showNativeModal', {"message": message});
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The string &lt;code&gt;showNativeModal&lt;/code&gt; in the &lt;code&gt;methodChannel.invokeMethod&amp;lt;String&amp;gt;&lt;/code&gt; function represents the name of the method to be executed on the Android or iOS side. Additionally, &lt;code&gt;{"message": message}&lt;/code&gt; serves as the parameter being sent along with this method call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Add the showNativeModal function to the beautiful_native_modal.dart file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import 'beautiful_native_modal_platform_interface.dart';

class BeautifulNativeModal {
  Future&amp;lt;void&amp;gt; showNativeModal(String message) {
    return BeautifulNativeModalPlatform.instance.showNativeModal(message);
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Plugins in Android
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Open the &lt;code&gt;BeautifulNativeModalPlugin.kt&lt;/code&gt; file in the &lt;code&gt;android&lt;/code&gt; folder and add the function to call the dialog from Android.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

private fun showNativeModal(message: String?) {
        val builder: AlertDialog.Builder = AlertDialog.Builder(activity)
        builder.setTitle("Modal")
        builder.setMessage(message)
        builder.setPositiveButton("OK") { dialog, _ -&amp;gt; dialog.dismiss() }
        val dialog: AlertDialog = builder.create()
        dialog.show()
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Call the showNativeModal function from the onMethodCall method.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == "showNativeModal") {
            val message = call.argument&amp;lt;String?&amp;gt;("message")
            showNativeModal(message)
            result.success(null)
        } else {
            result.notImplemented()
        }
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Then, the full code will look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.example.beautiful_native_modal

import android.app.Activity
import android.app.AlertDialog
import android.content.DialogInterface
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result

/** BeautifulNativeModalPlugin */
class BeautifulNativeModalPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel
    private var activity: Activity? = null

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "beautiful_native_modal")
        channel.setMethodCallHandler(this)
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        activity = binding.activity
    }

    override fun onDetachedFromActivityForConfigChanges() {
        activity = null
    }

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
        activity = binding.activity
    }

    override fun onDetachedFromActivity() {
        activity = null
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == "showNativeModal") {
            val message = call.argument&amp;lt;String?&amp;gt;("message")
            showNativeModal(message)
            result.success(null)
        } else {
            result.notImplemented()
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }

    private fun showNativeModal(message: String?) {
        val builder: AlertDialog.Builder = AlertDialog.Builder(activity)
        builder.setTitle("Modal")
        builder.setMessage(message)
        builder.setPositiveButton("OK") { dialog, _ -&amp;gt; dialog.dismiss() }
        val dialog: AlertDialog = builder.create()
        dialog.show()
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Run and test your code&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2risl4rwwmv09qo6x7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2risl4rwwmv09qo6x7t.png" alt="Android Dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Plugins in iOS
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Open the &lt;code&gt;BeautifulNativeModalPlugin.swift&lt;/code&gt; file in the &lt;code&gt;ios&lt;/code&gt; folder and add the function to display the modal in native iOS.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

private func showNativeModal(message: String) {
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        let defaultAction = UIAlertAction(title: "OK", style: .default) { _ in }
        alert.addAction(defaultAction)

        guard let controller = controller else {
            return
        }

        controller.present(alert, animated: true)
      }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Call the showNativeModal function from the handle method.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
   switch call.method {
          case "showNativeModal":
          if let args = call.arguments as? [String: Any],
             let message = args["message"] as? String {
              showNativeModal(message: message)
              result(nil)
          } else {
              result(FlutterError(code: "INVALID_ARGUMENT",
                                   message: "Missing or invalid argument",
                                   details: nil))
          }
          default:
              result(FlutterMethodNotImplemented)
          }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Then the full code will look like this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import Flutter
import UIKit

public class SwiftBeautifulNativeModalPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "beautiful_native_modal", binaryMessenger: registrar.messenger())
    let instance = SwiftBeautifulNativeModalPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
     switch call.method {
            case "showNativeModal":
            if let args = call.arguments as? [String: Any],
               let message = args["message"] as? String {
                showNativeModal(message: message)
                result(nil)
            } else {
                result(FlutterError(code: "INVALID_ARGUMENT",
                                     message: "Missing or invalid argument",
                                     details: nil))
            }
            default:
                result(FlutterMethodNotImplemented)
            }
  }

   private func showNativeModal(message: String) {
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        let defaultAction = UIAlertAction(title: "OK", style: .default) { _ in }
        alert.addAction(defaultAction)

        guard let controller = controller else {
            return
        }

        controller.present(alert, animated: true)
      }

   private var controller: UIViewController? {
     return UIApplication.shared.keyWindow?.rootViewController
   }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Run and test your plugin.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcu9sbv70j1p6uos5cnhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcu9sbv70j1p6uos5cnhb.png" alt="Ios Dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish package/plugin to &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you want to share your packages/plugins with the Flutter community, publishing it on &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; allows other developers to easily discover and use your package.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; Account&lt;/strong&gt;&lt;br&gt;
To publish packages on &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt;, you need to create an account. Visit the &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; website and sign in with your Google account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Prepare Your Pubspec&lt;/strong&gt;&lt;br&gt;
Ensure your pubspec.yaml file contains accurate metadata for your package, including the name, version, description, author, homepage, and dependencies. You can also include tags to help users discover your package.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Publishing Your Package&lt;/strong&gt;&lt;br&gt;
Once your package is ready, Run the command &lt;code&gt;flutter pub publish --dry-run&lt;/code&gt; then run flutter &lt;code&gt;pub publish&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;For more details on publishing, see the &lt;a href="https://dart.dev/tools/pub/publishing" rel="noopener noreferrer"&gt;publishing docs&lt;/a&gt; on dart.dev.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, Flutter packages and plugins are powerful tools. Developers can save time and effort by reusing code across projects. This eliminates the need to rewrite the same functionality from scratch. By utilizing packages from &lt;a href="https://pub.dev" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; or creating custom packages and plugins, developers can enhance productivity, reuse code, and access native features seamlessly across different platforms.&lt;/p&gt;

&lt;p&gt;that's it, thankyou&lt;/p&gt;

&lt;p&gt;Reference: &lt;br&gt;
&lt;a href="https://docs.flutter.dev/packages-and-plugins/developing-packages" rel="noopener noreferrer"&gt;Developing packages &amp;amp; plugins&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.flutter.dev/platform-integration/platform-channels" rel="noopener noreferrer"&gt;Writing custom platform-specific code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
