Thursday, October 10, 2019

Flutter rounded textfield




Snipcode

TextField(
  onChanged: bloc.validateEmail.push,
  autofocus: true,
  decoration: InputDecoration(
    filled: true,
    fillColor: Colors.white,
    contentPadding:
        EdgeInsets.only(left: 14.0, bottom: 8.0, top: 8.0),
    focusedBorder: OutlineInputBorder(
      borderSide: BorderSide(color: Colors.white),
      borderRadius: BorderRadius.circular(25.7),
    ),
    enabledBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: Colors.white),
      borderRadius: BorderRadius.circular(25.7),
    ),
    hintText: 'Email',
  ),
),



Preview

Share:

Monday, October 7, 2019

Flutter: Offline caching

After call rest api, you need to cache it on local for offline use.

Let’s begin!

## About caching implementation please look at: https://pub.dev/packages/bflutter/versions/0.1.2

## Install bflutter by add to pubspec.yaml 
dependencies:
  bflutter: ^0.1.2

## Create a net_cache model for unify model from local and network
/// Cache data from the Network model
class NetCache<T> {
  final T data;
  final dynamic error;
  bool fromNet = false;

  bool get hasData => data != null;
  bool get hasError => error != null;

  NetCache({bool fromNet, this.data, this.error}) {
    if (fromNet != null) this.fromNet = fromNet;
  }
}

## Flow fetching data

User make an request action to get data, I use Observable.merge for this case. It will merge 2 streams from network and from local storage, which one come first then show it first, but remember the cached data just for display only, and the data from the network is final.

// @nhancv 10/7/2019: Combine with cache data
return Observable<NetCache<List<User>>>.merge([
  // Get data from api
  Observable.fromFuture(Api().searchUsers(input))
      .asyncMap((data) async {
    print('From net: $data');
    if (data == null) {
      return NetCache(fromNet: true, data: <User>[]);
    }
    if (data.statusCode == 200) {
      final List<User> result = json
          .decode(data.body)['items']
          .cast<Map<String, dynamic>>()
          .map<User>((item) => User.fromJson(item))
          .toList();
      // @nhancv 10/7/2019: Storage data from network to local
      await BCache().insert(Piece(id: input, body: data.body));


      // @nhancv 10/7/2019: Return latest data from network
      return NetCache(fromNet: true, data: result);
    } else {
      throw (data.body);
    }
  }).handleError((error) {}),
  // Get data from local storage
  Observable.fromFuture(BCache().queryById(input)).map((data) {
    print('From cache: $data');
    if (data == null) {
      return NetCache(data: <User>[]);
    }
    List<User> result = json
        .decode(data.body)['items']
        .cast<Map<String, dynamic>>()
        .map<User>((item) => User.fromJson(item))
        .toList();
    return NetCache(data: result);
  })
]);

Demo:









Share:

Friday, October 4, 2019

Flutter state management with BLoC

This article presents how to implement BLoC in Flutter. 

The context on example: 

  • Give an edit text to get data input
  • Handle changing the text to get final search text
  • Deal with focus issues
  • Call Rest API to get data and stream it to the UI without setState


About State Management in Flutter please read my old article on medium here:
https://medium.com/beesightsoft/flutter-state-management-2455c60cc423

Let's begin



The UI has 3 main parts: Input text, Loading progress, and the result list or 'no data' text

Input text

                  child: TextField(
                    onChanged: bloc.searchUser.push,
                    autofocus: true,
                    decoration: InputDecoration(
                      border: InputBorder.none,
                      hintText: 'Please enter a search term',
                    ),
                  ),

Loading view

          Container(
            child: StreamBuilder(
              stream: bloc.loading.stream,
              builder: (context, loading) {
                if (loading.hasData && loading.data) {
                  return Center(
                    child: CircularProgressIndicator(),
                  );
                }
                return Container();
              },
            ),
          ),

The result part

          Expanded(
            child: StreamBuilder(
              stream: bloc.searchUser.stream,
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return Text(snapshot.error.toString());
                }
                if (!snapshot.hasData || (snapshot?.data)?.length == 0) {
                  return Text('No data');
                }
                List<UserBase> users = snapshot.data;
                return ListView.builder(
                  itemCount: users.length,
                  itemBuilder: (BuildContext context, int index) {
                    return FlatButton(
                      child: Row(
                        children: <Widget>[
                          CircleAvatar(
                            backgroundImage:
                                NetworkImage(users[index].avatarUrl),
                            radius: 20.0,
                          ),
                          Padding(
                            padding: EdgeInsets.only(left: 10, right: 10),
                            child: Text('${users[index].login}'),
                          ),
                        ],
                      ),
                      onPressed: () {
                        Navigator.push(
                            context,
                            MaterialPageRoute(
                                builder: (context) =>
                                    DetailScreen(userBase: users[index])));
                      },
                    );
                  },
                );
              },
            ),
          ),

Next is the BLoC Design Pattern (Business Logic Component)

import 'package:rxdart/rxdart.dart';

class Bloc<I, O> {
  // @nhancv 2019-10-04: Input data driven
  final BehaviorSubject<I> _inputSubject = BehaviorSubject<I>();

  // @nhancv 2019-10-04: Output data driven
  final BehaviorSubject<O> _outputSubject = BehaviorSubject<O>();

  // @nhancv 2019-10-04: Dynamic logic
  // Transfer data from input to mapper to output
  set logic(Observable<O> Function(Observable<I> input) mapper) {
    mapper(_inputSubject).listen(_outputSubject.sink.add);
  }

  // @nhancv 2019-10-04: Push input data to BLoC
  void push(I input) => _inputSubject.sink.add(input);

  // @nhancv 2019-10-04: Stream output from BLoC
  Stream<O> get stream => _outputSubject;

  // @nhancv 2019-10-04: Dispose BLoC
  void dispose() {
    _inputSubject.close();
    _outputSubject.close();
  }

}

BLoC will take input from UI and mapping input to output data type and stream it to UI via outputSubject.
I created 2 separated subjects to solve the focus issues which explained here https://www.nhancv.com/2019/10/flutter-can-not-focus-to-text-field.html In the navigated widget the widget will be rebuilt reflect with input focus state. In BLoC implemented above, when user input to a text field, the input subject will send it to the mapper function to the output format and push it to output subject. BehaviorSubject will cache the latest data, when widget rebuilds the stream just take the latest result and render without run mapper again.

The SearchBloc

import 'dart:convert';

import 'package:bflutter/bflutter.dart';
import 'package:bflutter_poc/api.dart';
import 'package:bflutter_poc/model/user_base.dart';
import 'package:flutter/cupertino.dart';
import 'package:rxdart/rxdart.dart';

class SearchBloc {
  final loading = BlocDefault<bool>();
  final searchUser = Bloc<String, List<UserBase>>();

  SearchBloc() {
    _initSearchUserLogic();
  }

  void _initSearchUserLogic() {
    searchUser.logic = (Observable<String> input) => input
            .distinct()
            .debounceTime(Duration(milliseconds: 500))
            .flatMap((input) {
          //show loading
          loading.push(true);
          if (input.isEmpty) return Observable.just(null);
          return Observable.fromFuture(Api().searchUsers(input));
        }).map((data) {
          if (data == null) {
            return <UserBase>[];
          }

          if (data.statusCode == 200) {
            final List<UserBase> result = json
                .decode(data.body)['items']
                .cast<Map<String, dynamic>>()
                .map<UserBase>((item) => UserBase.fromJson(item))
                .toList();
            return result;
          } else {
            throw (data.body);
          }
        }).handleError((error) {
          loading.push(false);
          throw error;
        }).doOnData((data) {
          loading.push(false);
        });
  }

  void dispose() {
    loading.dispose();
    searchUser.dispose();
  }
}

How to use SearchBloc on screen

class ___SearchInfoState extends State<_SearchInfo> {
  final bloc = SearchBloc();

  @override
  void dispose() {
    bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {}

}


Completed source here 

Look the video



Share:

Postgresql benchmark


Command
pgbench -i -p 5432 -d defaultdb -U admin
pgbench -p 5432 -d defaultdb -U admin -c 10
pgbench -p 5432 -d defaultdb -U admin -c 50 -T 60 -S -n
nano /tmp/bench.sql
CREATE TABLE IF NOT EXISTS test_bench (id int8, data varchar);
INSERT INTO test_bench VALUES(1,'test');
INSERT INTO test_bench VALUES(1,'test');
SELECT * FROM test_bench WHERE id=1;
SELECT * FROM test_bench WHERE id=2;


pgbench -p 5432 -d defaultdb -U admin -c 50 -T 60 -S -n -f /tmp/bench.sql

Preparing

Bench with 10 client

Bench with 50 client in 60s