Home Blog

User Data Policy

0

1. Introduction

This policy describes how Korean Easy collects, uses, handles, and protects user data. We are committed to protecting your privacy and ensuring transparent data practices in compliance with both Google Play Store and Apple App Store policies, as well as applicable laws.

2. Data Collection and Usage

2.1 Types of Data We Collect

We collect and handle the following types of data:

  • Device information (e.g., device model, operating system version, unique device identifiers)
  • App usage data (e.g., app launches, usage patterns, features used)
  • User-provided data (e.g., feedback, support requests)
  • [List any other specific data types your app collects]

2.2 Purpose of Data Collection

All data collection is limited to:

  • Core app functionality
  • Essential service features
  • Analytics and app performance
  • Policy-compliant purposes reasonably expected by users

2.3 Data Security

We implement the following security measures:

  • Modern cryptography for data transmission (HTTPS)
  • Secure data storage practices
  • Regular security audits and updates
  • Data encryption at rest and in transit

3.1 Platform-Specific Permissions

Android Devices

  • We request runtime permissions before accessing protected data
  • All permission requests include clear explanations of their purpose
  • Users can revoke permissions through device settings

iOS Devices

  • We request necessary permissions through iOS permission dialogs
  • App Tracking Transparency (ATT) consent is obtained when required
  • Privacy nutrition labels are accurately maintained in App Store

3.2 Prominent Disclosure

For any data collection or usage, we provide:

  • Clear in-app disclosures
  • Explicit consent requests
  • Detailed explanations of data usage
  • Transparent information about data sharing
  • Option to opt-out where applicable

4. Third-Party Integration

4.1 Third-Party Services

  • All integrated third-party services (SDKs) comply with platform policies
  • Third parties are contractually bound to protect user data
  • We ensure third parties do not sell personal and sensitive user data
  • Third-party data handling practices are monitored for compliance

4.2 Data Sharing

We do not:

  • Sell personal or sensitive user data
  • Share data with unauthorized parties
  • Use data for undisclosed purposes
  • Track users across apps without consent

5. User Rights and Control

5.1 Account Management

Users can:

  • Request account deletion
  • Access their collected data
  • Update their privacy preferences
  • Opt-out of optional data collection
  • Control tracking preferences

5.2 Data Deletion

Upon account deletion:

  • All associated user data is removed
  • Third-party services are notified to remove data
  • Required data retention periods are clearly communicated

6. Special Data Handling

6.1 Sensitive Data

Special handling procedures for:

  • Financial information
  • Government identification numbers
  • Health data
  • Children’s data (COPPA compliance)

6.2 Device Identifiers

  • Android: Advertising ID and App Set ID usage complies with Google Play requirements
  • iOS: IDFA usage complies with App Tracking Transparency framework
  • Persistent device identifiers are not linked to personal data without consent

7. Platform-Specific Requirements

7.1 Apple App Store

  • Privacy nutrition labels are maintained and accurate
  • App Tracking Transparency framework is implemented
  • Sign in with Apple is supported where applicable
  • Data collection minimization principles are followed

7.2 Google Play Store

  • Data safety section is accurately maintained
  • Advertising ID policies are followed
  • Prominent disclosures are provided as required
  • Personal and sensitive data handling complies with requirements

8. Contact Information

  • samderlust@gmail.com

9. Updates to This Policy

We will notify users of any material changes to this policy through:

  • In-app notifications
  • Email communications (if applicable)
  • App store updates

10. Compliance

This policy complies with:

  • Google Play Developer Program Policies
  • Apple App Store Review Guidelines
  • App Tracking Transparency requirements
  • Applicable privacy and data protection laws (GDPR, CCPA, etc.)
  • Children’s Online Privacy Protection Act (COPPA) if applicable

Last Updated: 2025-02-11

Calendar Day View 

0

A fully customizable Calendar day view library 

This package is dedicated to calendar day view. While there are many calendar packages out there. It seems that not many of them support Day view well. This package clearly is not a calendar replacement but rather a complement to make the calendar in app better. This package aims to give users the most customization they want.

Features 

  • Over flow day view: like normal calendar where event is displayed expanded on multiple time row to indicate its duration
  • In row day view: show all events that start in the same time gap in a row
  • Event day view: show all events in day
  • Option to change start of day and end of day in day view
  • Option to change time gap(duration that a single row represent) in day view.
  • Option to show current time on day view as a line

Installing and import the library: 

Like any other package, add the library to your pubspec.yaml dependencies:

dependencies:
    calendar_day_view: <latest_version>

Then import it wherever you want to use it:

import 'package:fetching_state/calendar_day_view.dart';

Usage 

look at example folder for all use cases

1. Overflow Day View 

  • For viewing event and duration as event will be shown on multiple time point rows depends on its own duration. For this to work all [DayEvent] must have non-null end time.
Overflow normalOverflow with ListView
Overflow Day ViewOverflow Day View
renderRowAsListView: falserenderRowAsListView: true
OverFlowCalendarDayView(
            events: events,
            dividerColor: Colors.black,
            startOfDay: const TimeOfDay(hour: 00, minute: 0),
            endOfDay: const TimeOfDay(hour: 23, minute: 0),
            timeGap: 60,
            renderRowAsListView: true,
            showCurrentTimeLine: true,
            showMoreOnRowButton: true,
            overflowItemBuilder: (context, constraints, event) {
              return <<ItemWidget>>
            },
          )

Event Only Day View 

  • For Viewing events only and their starts time
event only day view
 EventCalendarDayView(
      events: events,
      eventDayViewItemBuilder: (context, event) {
        return Container(
          color: getRandomColor(),
          height: 50,
          child: Text(event.value),
        );
      },
    );

In Row Day View 

  • For viewing events that starts in a same time window (15min, 30mins,…)
In Row Day View
InRowCalendarDayView<String>(
            events: events,
            heightPerMin: 1,
            showCurrentTimeLine: true,
            dividerColor: Colors.black,
            timeGap: 15,
            showWithEventOnly: withEventOnly.value,
            startOfDay: const TimeOfDay(hour: 00, minute: 0),
            endOfDay: const TimeOfDay(hour: 22, minute: 0),
            itemBuilder: (context, constraints, event) => Flexible(
              child:<<ITEM WIDGET>>
            ),
          ),

repo: https://github.com/samderlust/calendar_day_view

pub: https://pub.dev/packages/calendar_day_view

Geometry Kit 

0

A set of utils that help with geometry (line, circle, triangle, polygon,…)

Features 

  • distance from a point to a line
  • check if to line segments intersect
  • find intersect point of 2 line segments
  • get area or perimeter of shapes,
  • calculate circumcircle or incircle of a polygon
  • check if a point is in side a polygon

Installing the library: 

Like any other package, add the library to your pubspec.yaml dependencies:

dependencies:
    geometry_kit: <latest_version>

Then import it wherever you want to use it:

import 'package:fetching_state/fetching_state.dart';

Usage 


  // Line
  final line1 = Line(Point(0, 2), Point(2, 0));
  final line2 = Line(Point(0, -1), Point(3, 2));

  final intersect = LineUtils.getSegmentIntersect(line1, line2);

  print(intersect); //Point(1.5, 0.5)
//Polygon
  final polygon = [
    Point(1, 0),
    Point(0, 2),
    Point(0, 3),
    Point(2, 5),
    Point(3, 5),
    Point(5, 3),
    Point(5, 1),
    Point(3, 0),
  ];

  final point1 = Point<num>(5, 2);
  var isInside = PolygonUtils.isInsidePolygon(point1, polygon);
  print(isInside); // true

repo: https://github.com/samderlust/geometry_kit

pub: https://pub.dev/packages/geometry_kit

Fetching State

0

Fetching State 

A small package that helps easily to work with UI changes base on the state of fetching remote data

Features 

  • Get rid of if else statements in UI when state change. Hence, cleaner UI code
  • Decide what to display when fetching remote data in 4 states [init, loading,done, error, loadingMore]
  • Option to pass the data or error objects in onDone and onError

Getting started 

Installing the library: 

Like any other package, add the library to your pubspec.yaml dependencies:

dependencies:
    fetching_state:

Then import it wherever you want to use it:

import 'package:fetching_state/fetching_state.dart';

Usage 

see full example in example folder

1 . FetchingState 


  Future<void> getDone() async {
    setState(() {
      _fetching = _fetching.copyWhenLoading();
    });
    await Future.delayed(const Duration(milliseconds: 500));

    setState(() {
      _fetching = _fetching.copyWhenDone(data: 'DONE IN STATE');
    });
  }

  Future<void> loadMoreText() async {
    setState(() {
      _fetching = _fetching.copyWhenLoadingMore();
    });

    await Future.delayed(const Duration(milliseconds: 500));

    if (_fetching.data == null) {
      setState(() {
        _fetching = _fetching.copyWhenError(error: 'No current data');
      });
      return;
    }

    setState(() {
      _fetching =
          _fetching.copyWhenDone(data: '${_fetching.data} - extra text');
    });
  }

  Future<void> getError() async {
    setState(() {
      _fetching = _fetching.copyWhenLoadingMore();
    });
    await Future.delayed(const Duration(milliseconds: 500));

    setState(() {
      _fetching = _fetching.copyWhenError(error: 'Error IN STATE');
    });
  }

  Future<void> getInit() async {
    setState(() {
      _fetching = _fetching.copyWhenLoadingMore();
    });
    await Future.delayed(const Duration(milliseconds: 500));
    setState(() {
      _fetching = FetchingState.init(data: '');
    });
  }

1.2 capture change in UI 

 return _fetching.when(
    onInit: () => const Text(
      'INIT',
      style: TextStyle(color: Colors.blue),
    ),
    onDone: (text, isLoadingMore) => Text(
      '${text ?? ''} ${isLoadingMore ? '....' : ''}',
      style: const TextStyle(color: Colors.green),
    ),
    onError: (error) => Text(
      error!.toString(),
      style: const TextStyle(color: Colors.red),
    ),
    onLoading: () => const CircularProgressIndicator(),
  );

2. LoadStatus 

declare a class with LoadStatus mixin

class Counter with LoadStatusMixin {
  final int value;

  Counter(this.value);

  Counter copyWith({int? value}) {
    return Counter(value ?? this.value);
  }
}

manipulate state


  void increase() async {
    setState(() {
      _counter.setLoadStatusLoading();
    });

    await Future.delayed(const Duration(milliseconds: 500));
    setState(() {
      _counter = _counter.copyWith(value: _counter.value + 1);
      _counter.setLoadStatusDone();
    });
  }

UI implement:

  return _counter.whenOrElse(
      onLoading: () => const CircularProgressIndicator(),
      onDone: (_) => Text(_counter.value.toString()),
      onInit: () => const Text('Init'),
      onError: () => const Text('Error'),
      orElse: () => const Text('Nothing'),
    );

repo: https://github.com/samderlust/fetching_state

pub: https://pub.dev/packages/fetching_state

izetool

0

This is an open-source project to create a toolbox that helps you save time with boilerplate. Whatever your project is. The toolbox helps run command line to create your project and then create folders and files as follow the structure you provide

support: Linux, MacOS

Install

  1. Clone the project git clone https://github.com/samderlust/izetool.git
  2. run make izetool in the root folder to install the toolbox into your system
  3. run ize init to create ize_templates folder at home dir. This is where you store your template files

Available commands

commandusagenote
flutter createize flutter_create <name> --template=<template> or ize flutter_create <name>if --template is not provide, the example.json will be used as default template.
flutter uploadkeystoreize flutter uploadkeystoreprocess to create android upload keystore. After that, create key.properties file and also modify your app/build.gradle file.
makeize make <template> --name=<name>must provide a template.<template> is the same as file name

repo: https://github.com/samderlust/izetool

Introduction of fetching_state package

0

This package is inspired by the Union type provided by freezed package. But sometimes, it could be too complicating or you join a legacy project or a team project that it’s not ready to migrate to that package.

Fetching_state for simplification

Most of the time, in your app, you have to fetch remote data that would take a while after sending the request until obtaining the data. That is where and when you want to display something like a Loading Indicator and then either a successful fetched content or error content.

Normally, we would you some boolean state to indicate whether data is being fetched, another state for notifying error. Follow by some if else statements.

That’s why I create the package fetching_state. This package helps you to handle fetching state easier and cleaner, get rid of those if else statements when data is fetching, finished fetching, or error on fetching even an init state that lives before those ones.

Features of fetching_state package

  • Get rid of if else statements in UI
  • Decide what to display when fetching remote data in 4 states [initloading,doneerror]
  • Option to pass the data or error objects in onDone and onError

Usage of fetching_state

In this example, for the sake of simplification, we use a stateful widget. But this package and be used with any state management solution.

create a fetching state with init state _fetching = FetchingState.init();

and then, depending on the state of your app we can set it to appropriate status as follow

_fetching = FetchingState.loading();

_fetching = FetchingState.done()

_fetching = FetchingState.error()

In the build method, you can use when or whenOrElse method to build the corresponding widget.

For more options, you can pass data into FetchingState.done({T? data}) and FetchingState.error({E? error}). These data later can be accessed in: R Function(T? data) onDone, R Function(E? error) onError when you declare your when method as in the example.

return _fetching.when(
                    onInit: () => const Text(
                      'INIT',
                      style: TextStyle(color: Colors.blue),
                    ),
                    onDone: (text) => Text(
                      text!,
                      style: const TextStyle(color: Colors.green),
                    ),
                    onError: (error) => Text(
                      error!,
                      style: const TextStyle(color: Colors.red),
                    ),
                    onLoading: () => const CircularProgressIndicator(),
                  );

for more details, please visit: https://pub.dev/packages/fetching_state

Full Example

import 'package:fetching_state/fetching_state.dart';
import 'package:flutter/material.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetchin State Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textTheme: const TextTheme(
          bodyText2: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
      home: const Example(),
    );
  }
}

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

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  late FetchingState<String, String> _fetching;
  @override
  void initState() {
    _fetching = FetchingState.init();
    super.initState();
  }

  Future<void> getDone() async {
    setState(() {
      _fetching = FetchingState.loading();
    });
    await Future.delayed(const Duration(milliseconds: 500));

    setState(() {
      _fetching = FetchingState.done(data: 'DONE IN STATE');
    });
  }

  Future<void> getError() async {
    setState(() {
      _fetching = FetchingState.loading();
    });
    await Future.delayed(const Duration(milliseconds: 500));

    setState(() {
      _fetching = FetchingState.error(error: 'Error IN STATE');
    });
  }

  Future<void> getInit() async {
    setState(() {
      _fetching = FetchingState.loading();
    });
    await Future.delayed(const Duration(milliseconds: 500));
    setState(() {
      _fetching = FetchingState.init();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Center(
              child: Builder(
                builder: (context) {
                  return _fetching.when(
                    onInit: () => const Text(
                      'INIT',
                      style: TextStyle(color: Colors.blue),
                    ),
                    onDone: (text) => Text(
                      text!,
                      style: const TextStyle(color: Colors.green),
                    ),
                    onError: (error) => Text(
                      error!,
                      style: const TextStyle(color: Colors.red),
                    ),
                    onLoading: () => const CircularProgressIndicator(),
                  );
                },
              ),
            ),
            const SizedBox(
              height: 40,
            ),
            ElevatedButton(
              onPressed: getDone,
              child: const Text('Done'),
            ),
            ElevatedButton(
              onPressed: getError,
              child: const Text('Error'),
            ),
            ElevatedButton(
              onPressed: getInit,
              child: const Text('Init'),
            ),
          ],
        ),
      ),
    );
  }
}

Flutter Hooks – scratching the surface

0

With previous experience working with React, moving to Flutter, hooks are things that I feel missing the most. Hooks help a lot in dealing with states and the life cycles of a component. Luckily, there is a package that brings hooks to Flutter: flutter_hooks. Flutter now feels so familiar.

The code written in this post is at my GitHub repo

Create a Stateful Widget

Well. we’re all (I mean those who write Flutter) know what it does and how to create it. The code snippet below shows a simple screen using the stateful widget. We have an int state _number that is initialized as 0;


class HookDemo extends StatefulWidget {
  @override
  _HookDemoState createState() => _HookDemoState();
}

class _HookDemoState extends State<HookDemo> {
  int _number = 0;
  @override
  void initState() {
    super.initState();
    _number = 1;
    print('hello');
  }

  @override
  void dispose() {
    super.dispose();
    print('good bye');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _number++;
          });
        },
        child: Icon(Icons.add),
      ),
      body: Center(
        child: Text(
          _number.toString(),
          style: TextStyle(fontSize: 30),
        ),
      ),
    );
  }
}

We have initState which will be called at the very beginning this widget mounts, and for some silly reason, we set the _number to be 1 as well as printing “hello”. So that as soon as the screen is loaded, the Text widget in the body shows 1, and the console prints “hello”.

In addition, we have a dispose method which will be called when this widget is unmounted to release the resource or in this case, saying “good bye”.

We also have a FloatingActionButton which will increase the _number by one every time we tap it. Thanks to the setState method, it triggers the build method so that the widget is rebuilt to reflect the change of _number (the state).

At this point, you feel that you’re remembering how easy hooks are in React.js. And would using hooks here, in Flutter, can be as easy and less verbose?

Creating a hook widget

Let start by converting the previous example to a hook widget. It can be done as easily as extends the widget from HookWidget. Since a HookWidget doesn’t have initState or dispose, we can eradicate those 2 methods. Of course, we can’t use setState anymore, it’s now an orphan.

Don’t forget to import flutter_hooks import ‘package:flutter_hooks/flutter_hooks.dart’;

useState and useEffect

Now let’s revive the long-lost friend from React, useState. The usage is just as similar. In this case, we create a number state with the type of <int> and init value of <0>.

final number = useState<int>(0);

The other friend is useEffect, again, similar syntax. useEffect has 2 parameters, first one is the effect function that will be called the first time this widget is built and every time the dependency changes. The second parameter is an array of dependencies, which if any of the dependencies we put here changes, the effect function will be called. However, If we leave the array empty, the effect function is trigger only once when the widget mounts, kinda same effect as initState.

effect function returns another function, this is the dispose function which will be called at the time the widget unmounts.

    useEffect(
      () {
        print('hello');
        return () {
          print('good bye');
        };
      },[]
    );

Never, without a purpose, leave out the second parameter unless the effect function will be triggered every time any state changes. 

Remember, all hooks must be declared inside the build method.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class HookDemo extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final number = useState<int>(0);

    useEffect(
      () {
        print('hello');
        return () {
          print('good bye');
        };
      },
    );

    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () => number.value++,
        child: Icon(Icons.add),
      ),
      body: Center(
        child: Text(
          number.value.toString(),
          style: TextStyle(fontSize: 30),
        ),
      ),
    );
  }
}

Available hooks

If you think despite that similar to React hooks, it’s not enough to convince you to use Flutter hooks, there are more than just useState and useEffect. If you had a hard time building a widget with multiple controllers (AnimatationContrller, ScrollController,.. etc) with declaring and disposing, Flutter Hooks would be a no-brainer choice.

For example, create 3 AnimationController using hooks is as simple as this: 

 final controller = useAnimationController();
 final controller2 = useAnimationController();
 final controller3 = useAnimationController();

useAnimationController will automatically create and dispose the controller for you. Stress free!

Below are some of the available hooks. Moreover, you can create your custom hooks!!!

useEffectUseful for side-effects and optionally canceling them.
useStateCreate variable and subscribes to it.
useMemoizedCache the instance of a complex object.
useContextObtain the BuildContext of the building HookWidget.
useValueChangedWatches a value and calls a callback whenever the value changed.

More can be found at flutter_hook

Conclusion

Flutter hooks in some way, make our life as flutter developers easier when we can get rid of verbose syntaxes and boilerplates. Or if you are like me, a former React developer, it brings you ease and joy and memories,…

Flutter – The use of Selector in Provider package

When it comes to state management in Flutter, most of the time people prefer to use Provider.  A package that is most liked in pub.dev, was created by Remi, and is recommended by Flutter team. Provider is in its version 4 now and had a lot of changes as well as improvements in the past. There actually are so many posts and articles that you can find on the Internet on how to use this package. So in this post, I’m not going to iterate that loop.

source code of this post can be found here: github

The use of Consumer:

If you’ve been using Provider you probably know why and how to use Consumer. Basically, it’s the way to narrow down the widget you need to rebuild on each state update. It is ideal to use Consumer as deep in your widget tree as possible so that reduces the amount of unnecessary re-render.

Put into the context:

What if you have more than 1 thing that could be changed in your provider? That means when one value changes and notifyListeners() is called all widgets that listen to the other values in the same ChangeNotifier will be triggered to rebuild as well. And Consumer doesn’t help in this case since it listens to a whole Provider, not a particular value.

Let’s examine the code of a provider class below:

class NumberProvider extends ChangeNotifier {
  int _number1 = 0;
  int _number2 = 1;
  int get number1 => _number1;
  int get number2 => _number2;

  void add() {
    _number1++;
    _number2++;
    notifyListeners();
  }

  void addTo1() {
    _number1++;
    notifyListeners();
  }

  void addTo2() {
    _number2++;
    notifyListeners();
  }
}

And here a screen that displays number 1 and 2


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

  @override
  Widget build(BuildContext context) {
    print('build home');
    return Container(
      child: Scaffold(
        floatingActionButton: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            FloatingActionButton(
              heroTag: 'all',
              onPressed: () {
                context.read<NumberProvider>().add();
              },
              child: Text('all'),
            ),
            FloatingActionButton(
              heroTag: '1',
              onPressed: () {
                context.read<NumberProvider>().addTo1();
              },
              child: Text('1'),
            ),
            FloatingActionButton(
              heroTag: '2',
              onPressed: () {
                context.read<NumberProvider>().addTo2();
              },
              child: Text('2'),
            ),
          ],
        ),
        body: SafeArea(
          child: Center(
            child: Container(
              child: Consumer<NumberProvider>(
                builder: (context, provider, child) {
                  print('rebuild consumer');
                  return Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Container(
                        color: Colors.red,
                        padding: EdgeInsets.all(10),
                        child: Text('${provider.number1}'),
                      ),
                      Container(
                        color: Colors.green,
                        padding: EdgeInsets.all(10),
                        child: Text('${provider.number2}'),
                      ),
                    ],
                  );
                },
              ),
            ),
          ),
        ),
      ),
    );
  }
}

I created 3 methods in the provider class that will update both numbers simultaneously or separately. But since the screen using Consumer and listens NumberProvider, it doesn’t matter which button we tap on. The widgets that display number1 and number2 both get rebuilt. In a large scale application, this leads to a waste of resource.

Selector for good:

Selector is a class in Provider package that is less known compare to Consumer yet useful. As the name suggests, Selector allows you to select a specific value in a Provider to listen to. Then when and only when that selected value changes, the widget that returns by the builder method of Selector will rebuild.

This is done by the selector function will compare the previous value and the new value whenever the provider notifies something. If the selected value is changed, then the builder method is triggered. Pretty handy eh? 

Have a look at the code below:

Container(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Selector<NumberProvider, int>(
                      selector: (_, provider) => provider.number1,
                      builder: (context, number1, child) {
                        print('Build num1');
                        return Container(
                          color: Colors.red,
                          padding: EdgeInsets.all(10),
                          child: Text('$number1'),
                        );
                      }),
                  Selector<NumberProvider, int>(
                      selector: (_, provider) => provider.number2,
                      builder: (context, number2, child) {
                        print('Build num2');
                        return Container(
                          color: Colors.green,
                          padding: EdgeInsets.all(10),
                          child: Text('$number2'),
                        );
                      }),
                ],
              ),
            ),

Instead of using one Consumer, I used 2 Selectors for each number. Therefore, if number1 gets updated, only the respective widget rebuilds, the other widget that displays number2 stays still.

Addition to selector 

There are some variations of Selector that you can use to listen to more than one selected value such as: Selector1, Selector2,…

But there is another way you can do that using the Tuple package and is recommended by the author of Provider:

Selector<Foo, Tuple2<Bar, Baz>>(
  selector: (_, foo) => Tuple2(foo.bar, foo.baz),
  builder: (_, data, __) {
    return Text('${data.item1}  ${data.item2}');
  }
)

read more

Conclusion

Selector class helps you narrow down the scope of widgets that needs to be rebuilt and easier to manage what the widget is listening to. Make good use of this class and save your app some redundant rebuilds

White noise player

0

A white noise player

Technical Info:

See how it works in motion

Currently finding a free source of white noise

The Membership wallet

0

An App to hold barcodes cards in your phone.

Technical Info:

Update v2 with easy access to edit or delete