2017-02-16

Notepad++: Remove lines containing/not containing string/word

Đôi khi dùng Notepad++ để remove lines chứa string hay word nào đó có thể sử dụng Regular Expression với negative lookbehind. Dùng nhiều nhưng giờ mới note lại ở đây để nhớ.

Tham khảo link
http://stackoverflow.com/questions/1971738/regex-for-all-strings-not-containing-a-string
http://stackoverflow.com/questions/406230/regular-expression-to-match-a-line-that-doesnt-contain-a-word

Ví dụ muốn remove nhưng line chứa warning hay error thực hiện như sau

^((?!(error|warning)).)*$



Sau đó có thể remove empty lines dùng Edit » Line Operation » Remove Empty Lines hay TextFX » TextFX Edit » Delete Blank Lines.

Nếu ngược lại muốn remove tất cả các lines ngoại trừ các lines chứa string/word. Thực hiện replace RegEx với empty như sau:

(?!^.*sometext.*$)^.+\r?\n
https://superuser.com/questions/290247/delete-all-lines-in-notepad-except-lines-containing-a-word-i-need/1318767#1318767?newreg=552307d560554536922266c54ac56cb7

2017-01-11

Tại sao chiều cao gần như tuân theo phân phối chuẩn ...?

Trong hầu hết các sách về lý thuyết xác xuất thống kê đều lấy ví dụ như vậy.

Đại khái là chiều cao của con người (hay đàn ông/phụ nữ ...) là một biến ngẫu nhiên tuân theo quy luật phân phối chuẩn. Hay khi textbook đề cập hầu hết các hiện tượng sinh học trong tự nhiên (or nhiều hiện tượng trong tự nhiên) gần như tuân theo phân phối chuẩn.

Khi học mình đến đây mình cũng thắc mắc ngay (trong đầu và để đó). Mới đầu mình cũng chưa hình dung vì sao, cứ để tạm đó xem, chắc có một lý do gì thôi. Sau đó chẳng thấy có giải đáp mẹ gì cả, cũng đặt câu hỏi tại sao lại như thế, nhưng cũng chẳng hỏi ai (ít lên lớp, lên cũng nằm ngáp ngáp). Và mình đã ngồi search kiếm câu trả lời. Gần đây có đứa em hỏi tại sao :D. Lâu rồi mình cũng mới thấy có người hỏi như vậy (trước đây khá lâu cũng có vài lần được hỏi...).

Thật ra có nhiều câu trả lời. Trên Quora thôi là đã rất nhiều (rất tiếc chẳng có phiên bản Quora tiếng Việt nào) mà hình như cũng không nhiều người xài Quora. Ví dụ:

Why do quantities in nature tend to be normally distributed, such as students' grade, human's height, size of snowflakes and so on. However, we find the returns of assets (like stocks, bonds, options) don't follow normal distribution (fat tail),How to explain it?

What are some real world examples of normally distributed quantities?


Bạn có thể tham khảo cả hai bài sau của John D. Cook (rất dễ hiểu)

Why hieghts are not normally distributed?


Mình tóm tắt lại bài đầu như sau:

Nếu chiều cao là một đặc tính di truyền đơn giản (simple genetic characteristic) thì sẽ quy định chiều cao là cao hoặc lùn (:D). Ví dụ tác giả nói rằng trong thí nghiệm di truyền của Mendel, Mendel có để là đậu nhăn nheo hoặc mịn chứ không để thuộc tính đậu hơi nhăn nheo. 

Có rất nhiều yếu tố di truyền và cả môi trường ảnh hưởng đến chiều cao. Theo đó nhiều yếu tố độc lập góp thành (sum) sẽ tạo nên một phân phối Gauss (Gaussian distribution) theo định lý giới hạn trung tâm (central limit theorem CLT).

2016-12-26

Castle Windsor Part 2 - Array configuration, dictionary configuration

Xem original

Part 2 – Array Configuration
Part 3 – Dictionary configuration

Array configuration

Đầu tiên là config array.

Ví dụ holiday service có code như sau:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using System;

namespace ConsoleApp
{
    public class HolidayService
    {
        private DateTime[] holidays;

        public DateTime[] Holidays
        {
            get { return holidays; }
            set { holidays = value; }
        }

        public bool IsHoliday(DateTime date)
        {
            if (holidays != null)
            {
                DateTime matchDate = date.Date;
                foreach (DateTime dt in Holidays)
                {
                    if (dt.Date.Equals(matchDate))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WindsorContainer container = new WindsorContainer(new XmlInterpreter());

            HolidayService holidayService = container.Resolve<HolidayService>();

            DateTime xmas = new DateTime(2016, 12, 25);
            DateTime newYears = new DateTime(2017, 1, 1);

            if (holidayService.IsHoliday(xmas))
            {
                Console.WriteLine("Merry X'mas!");
            }
            else
            {
                Console.WriteLine("X'mas is only for management!");
            }

            if (holidayService.IsHoliday(newYears))
            {
                Console.WriteLine("Happy new year!");
            }
            else
            {
                Console.WriteLine("New year, you haven't done all the work for last year!");
            }

            Console.ReadLine();
        }
    }
}
Config trong App.config như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component type="ConsoleApp.HolidayService, ConsoleApp">
        <parameters>
          <holidays>
            <array>
              <item>2016-12-24</item>
              <item>2016-12-25</item>
              <item>2017-1-1</item>
            </array>
          </holidays>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Có thể dùng <Holidays> hoặc <holidays> đều được vì Windsor đủ smart để inject. Nếu muốn resolve trực tiếp ra IList (hoặc IList, IEnumerable ... nói chung là generic collection)
static void Main(string[] args)
{
    WindsorContainer container = new WindsorContainer(new XmlInterpreter());
    var holidays = container.Resolve<IList<DateTime>>("holidays");
    Console.WriteLine(string.Join("\r\n", holidays));
    Console.ReadLine();
}
File config tương ứng như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="holidays" type="System.Collections.Generic.List`1[System.DateTime]">
        <parameters>
          <collection>
            <array>
              <item>2016-12-24</item>
              <item>2016-12-25</item>
              <item>2017-1-1</item>
            </array>
          </collection>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>

Cần nhớ ở dạng config này là thực hiện theo <parameters><collection><array><item>. Nếu đầy đủ hơn thì type chỉ định có cả assembly là <component id="holidays" type="System.Collections.Generic.List`1[[System.DateTime, mscorlib]], mscorlib">.

Dictionary configuration

Tương tự như array, dictionary configuration thực hiện với <parameters><dictionary><dictionary><entry>. Ví dụ AliasService như sau:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    public class AliasService
    {
        private Dictionary<string, string> dict;

        public Dictionary<string, string> Aliases
        {
            get { return dict; }
            set { dict = value; }
        }

        public string Evaluate(string term)
        {
            if (dict == null)
            {
                return term;
            }

            while (dict.ContainsKey(term))
            {
                term = dict[term];
            }

            return term;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WindsorContainer container = new WindsorContainer(new XmlInterpreter());

            AliasService aliasService = container.Resolve<AliasService>();
            string sentence = "A dog ate my homework";

            foreach (string word in sentence.Split(new char[] { ' ' }, 
                StringSplitOptions.RemoveEmptyEntries))
            {
                Console.Write("{0} ", aliasService.Evaluate(word));
            }

            Console.ReadLine();
        }
    }
}
App.config cấu hình dictionary:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component type="ConsoleApp.AliasService, ConsoleApp">
        <parameters>
          <Aliases>
            <dictionary>
              <entry key="dog">duck</entry>
              <entry key="ate">broke</entry>
              <entry key="homework">code</entry>
            </dictionary>
          </Aliases>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Để resolve trực tiếp ra IDictionary
static void Main(string[] args)
{
    WindsorContainer container = new WindsorContainer(new XmlInterpreter());
    var states = container.Resolve<IDictionary<string, string>>("states");
    Console.WriteLine(string.Join("\r\n", states.Keys));
    Console.ReadLine();
}
File config tương ứng như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="states" type="System.Collections.Generic.Dictionary`2[System.String, System.String]">
        <parameters>
          <dictionary>
            <dictionary>
              <entry key="VN-CT">Cần Thơ</entry>
              <entry key="VN-DN">Đà Nẵng</entry>
              <entry key="VN-HN">Hà Nội</entry>
              <entry key="VN-HP">Hải Phòng</entry>
              <entry key="VN-SG">Hồ Chí Minh</entry>
            </dictionary>
          </dictionary>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Tới đây là xong part 2. Về type convertor có thể tham khảo thêm phần Configuration with type converters trong series của Mike Hadlow '10 Advanced Windsor tricks'.

Container tutorials ... example with Castle Windsor. Part 1 - Configuration parameters.

Mình dùng Castle Windsor từ lâu. Cũng có note nhưng rồi để đâu mất. Mình đã tính viết 1 series làm sao đủ để xài Windsor từ cái thời còn dùng CAB/SCSF, sau đó là Prism nhưng rồi chẳng biết sao lúc đó bỏ nửa chừng.

Inversion of Control (IoC), Dependency Inversion Principle (DIP) và Dependency Injection (DI), CAB&SCSF cũng chỉ viết tới Part 03: Dependency Injection

Gần đây coi lại và thấy lần này nên note kỹ. Nói chung đầu tiên là lược dịch từ những tutorials trên NET bắt đầu từ series trên BitterCoder (đã rất lâu rồi từ tận 2007). Có thể đọc thêm hướng dẫn từ project chính thức trên Github castleproject/Windsor.

Gần đây có nhiều lựa chọn IoC container cho .NET gồm có Autofac, StructureMap, Unity. Một vài container có vẻ dần chiếm được nhiều quan tâm hơn như Autofac với simple API, dễ sử dụng và performance tốt. Tuy nhiên Windsor nói chung vẫn đáp ứng được yêu cầu đặt ra với nhiều module (facilities) từ logging, NHibernate, ASP.NET MVC ...

Configuration parameters

Part đầu tiên là configuration parameters. Windsor cho phép cấu hình component với parameters run-time. Mặc dù có thể configuration với .NET qua app.config bình thường nhưng sử dụng với Windsor khá là đơn giản và tiện dụng.

Tạo một Project ConsoleApp, dùng NuGet install Castle Windsor. Tạo file app.config.


Giả sử có một class là Tax, mặc định tax rate là 10%. Có thể config rate thông qua app.config.
public class Tax
{
    private decimal rate = 0.10m;

    public decimal Rate
    {
        set { rate = value; }
        get { return rate; }
    }

    public decimal Calculate(decimal gross)
    {
        return Math.Round(rate * gross, 2);
    }
}
Sau khi tạo class Tax, thực hiện dùng với Windsor như sau:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using System;

namespace ConsoleApp
{
    public class Tax
    {
        private decimal rate = 0.10m;

        public decimal Rate
        {
            set { rate = value; }
            get { return rate; }
        }

        public decimal Calculate(decimal gross)
        {
            return Math.Round(rate * gross, 2);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WindsorContainer container = new WindsorContainer(new XmlInterpreter());

            // Resolve
            Tax calculator = container.Resolve<Tax>();

            decimal gross = 100;
            decimal tax = calculator.Calculate(gross);

            Console.WriteLine("Gross: {0}, Tax: {1}", gross, tax);
            Console.ReadLine();
        }
    }
}
Container được tạo với XmlInterpreter() sẽ đọc configuration từ file app.config (hoặc web.config với web app), instance calculator được tạo bởi container thông qua Resolve().

Thực hiện Build và Run sẽ ra báo lỗi không có config section 'castle'.
Thực hiện thêm config section vào App.config. Giả sử App.config không có cấu hình component sẽ gây exception ComponentNotFoundException
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Thực hiện config component nhưng không set tax rate như sau
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="tax" type="ConsoleApp.Tax, ConsoleApp">
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Type có thể không cần dùng AssemblyQualifiedName mà để đơn giản là Tax hoặc ConsoleApp.Tax cũng OK. Kết quả trả ra 'Gross: 100, Tax: 10.00'.

Thực hiện setup rate bằng parameters như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="tax" type="ConsoleApp.Tax, ConsoleApp">
        <parameters>
          <rate>0.25</rate>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Kết quả trả ra 'Gross: 100, Tax: 25.00'.

Với id="tax" có thể dùng để resolve component khác nhau, ví dụ:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="tax1" type="ConsoleApp.Tax, ConsoleApp">
        <parameters>
          <rate>0.25</rate>
        </parameters>
      </component>
      <component id="tax2" type="ConsoleApp.Tax, ConsoleApp">
        <parameters>
          <rate>0.05</rate>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
và code
WindsorContainer container = new WindsorContainer(new XmlInterpreter());
decimal gross = 100;

// Resolve tax #1
Tax calculator1 = container.Resolve<Tax>("tax1");
decimal tax1 = calculator1.Calculate(gross);
Console.WriteLine("Gross: {0}, Tax: {1}", gross, tax1);

// Resolve tax #2
Tax calculator2 = container.Resolve<Tax>("tax2");
decimal tax2 = calculator2.Calculate(gross);
Console.WriteLine("Gross: {0}, Tax: {1}", gross, tax2);

Console.ReadLine();
Như vậy coi như là đủ cho part 1. Phần tiếp theo sẽ thực hiện config arrays, dictionary ...

2016-12-24

How to integrate SyntaxHighlighter to Blogger, Blogspot

Dạo gần đây post nhiều code, nhìn lại mới thấy blogger của mình post code rất ẹ. Vì vậy phải chuyển qua một dạng OK cho đồng nhất. Search một vòng và mình chọn Syntax Highlighter vì thấy nó đơn giản và nhìn rõ ràng. Hướng dẫn integrate vào Blogger/Blogspot thì có nhiều ngay tại trang chính SyntaxHighlighter.

Nói sơ qua một chút như sau. Thông thường khi viết code có thể đơn giản dùng tag blockquote được hỗ trợ sẵn <blockquote></blockquote> và post source vào đó. Tuy nhiên source sẽ không được highlight syntax và sẽ khó đọc 1 chút. Hay vẫn có thể dùng http://hilite.me/ như trước đây. Nhưng nói chung là vẫn cảm thấy không được tốt lắm.

Một cách khác là embbeded gist trên Github. Có thể tham khảo tại đây
Embedding Github code into your Blogger blog
SyntaxHighlighter là một các embedded trực tiếp dùng Javascript,  có thể download và dùng dễ dàng với bất kỳ website nào, không loại trừ Blogger/Blogspot. Nó giống như dạng MathJax.

Cơ bản như sau:
1. Download và put tại host/web server

2. Thực hiện theo các bước hướng dẫn tại http://alexgorbatchev.com/SyntaxHighlighter/manual/installation.html

3. Dùng config và brush alias tương ứng với language của source code http://alexgorbatchev.com/SyntaxHighlighter/manual/configuration/

Với Blogger/Blogspot, vào Template
1. Để chắc ăn thực hiện backup Template » Backup/Restore » Download template

2. Vào Edit HTML, trong tag <head> thực hiện include CSS, Javascript và init code cần thiết tùy theo version với link bắt đầu http://alexgorbatchev.com/pub/sh/. Version hiện tại là
3.0.83 ngày 2016/12/24. Hay có thể dùng CDN https://cdnjs.com/libraries/SyntaxHighlighter (Blogger dùng với https). Có thể dùng theme default, Eclipse hay midnight. Include các brush thông dụng như C++, Java, C#, Scala, Python, CSS, SQL, XML... Thực hiện config Blogger mode bật ON. Save và kiểm tra có lỗi Javascript hay không.

3. Thực hiện test bằng cách đơn giản nhất là dùng tag <pre>, tất nhiên là phải chuyển qua HTML code, xong copy source từ source file hay IDE hay text editor như Notepad++ qua. Nên bật ruler và tắt toolbar.

Set defaults như sau:
SyntaxHighlighter.defaults['tab-size'] = 4;
SyntaxHighlighter.defaults['toolbar'] = false;

Ngay sau khi gọi SyntaxHighlighter.all()

<pre class="brush: csharp; toolbar: false;">
using namespace System;
namespace Demo
{
    public class Pet
    {
        public string Name { get; set; }

        public Pet()
        {

        }
    }
}
</pre>

Kết quả như sau:

using namespace System;
namespace Demo
{
    public class Pet
    {
        public string Name { get; set; }

        public Pet()
        {

        }
    }
}

Cách thứ 2 là để trong tag script và bắt buộc có CDATA

<script type="syntaxhighlighter" class="brush: js"><![CDATA[
/**
 * SyntaxHighlighter
 */
function foo()
{
 if (counter <= 10) {
  return;
 }

 // it works!
}
]]></script>

Dùng first-line để chỉ định line number đầu tiên trên ruler và highlight dùng chỉ định highlight line number nào. Ví dụ:
<pre class="brush: php; toolbar: false; auto-links: false; first-line: 10; highlight: [11, 13]">
  /**
    * https://github.com/syntaxhighlighter
    */
  echo("https://github.com/syntaxhighlighter");
</pre>

Kết quả sẽ là

  /**
    * https://github.com/syntaxhighlighter
    */
  echo("https://github.com/syntaxhighlighter");

2016-11-30

Retained Fragment, headless Fragment là gì, setRetainInstance(true)?

Thực ra mình cũng định nhắc tới headless Fragment từ lâu. Cũng vài năm rồi, dạo gần đây có đụng lại Android, cần phải giải thích, có search 1 vòng nhưng vẫn không thấy một bài viết tiếng Việt nào có sẵn nên note luôn ở đây. Post này không bàn chi tiết về code, hay cụ thể trường hợp sử dụng headless Fragment nào chỉ focus chung vào đặc điểm của headless Fragment và một vài case thông dụng.

Headless Fragment là gì?

Đầu tiên là tên gọi headless Fragment? Nhiều developer khác gọi là viewless Fragment, UI-less Fragment, retained Fragment không có view hay nhiều khi còn gọi là retained "worker" Fragment lý do sẽ đề cập sau là Fragment này thông thường được sử dụng để manage một worker hay background operation.

Tất nhiên cái tên này làm liên tưởng nhiều đến kỵ sĩ không đầu headless horseman/knight.

Có nhiều developer thông tin rằng tên gọi này bắt nguồn từ tutorial của Lars Vogel tại
Multi-pane development in Android with Fragments - Tutorial#Headless Fragments.

Vậy headless Fragment cụ thể là gì? Mặc dù tên là headless Fragment nhưng tính năng đáng nói nhất lại là "retained" qua configuration change.

setRetainInstance(true)

Mặc định Fragment sẽ bị hủy (destroyed) và tạo lại (recreated) cùng với parent Activity. Việc sử dụng Fragment#setRetainInstance(true) sẽ giúp Fragment được giữ lại retained khi Activity được tạo lại (recreation).

LƯU Ý: điều này chỉ áp dụng với các Fragment KHÔNG NẰM TRONG BACK STACK.

Trạng thái của Fragment sẽ được retained (duy trì, giữ lại) khi configuration change. Điều này "retained" có nghĩa là Fragment sẽ không bị destroyed khi configuration changes. Cũng có nghĩa là Fragment sẽ được giữ lại ngay cả khi configuration change làm cho Activity bị destroyed.

Tất nhiên cần nói rõ thêm. Giống như Activity, Fragment vẫn có thể bị destroyed bởi hệ thống khi tài nguyên bộ nhớ xuống thấp. Khi đó dù có thiết lập "retained" hay không thì hệ thống cũng sẽ destroy các Fragment này khi rời khỏi Activity. Trong trường hợp rời khỏi Activity (ví dụ nhấn nút home), Fragment có thể có hoặc không bị hủy. Nhưng khi rời khởi Activity bằng nút back (việc đó sẽ gọi finish() và sẽ destroy Activity), tất cả các Fragment cũng sẽ bị destroy.

Xem thêm Understanding Fragment's setRetainInstance(boolean) trên Stackoverflow,
Handling configuration changes with Fragments của Alex Lockwood

UI-less + setRetainInstance(true)

Headless Fragment là Fragment không có/không định nghĩa UI hay có nghĩa không inflate một View nào. Tài liệu chính thức của Adroid nói về headless Fragment là Fragment without UI tại Fragments guide/Adding a fragment without a UI. Tuy nhiên trong đoạn này có một chỗ ko rõ ràng là 

"This adds the fragment, but, because it's not associated with a view in the activity layout, it does not receive a call to onCreateView(). So you don't need to implement that method".

Chính xác thì Android có gọi Fragment#onCreateView() nhưng kết quả trả về là null. Tức là thay vì không override Fragment#onCreateView() thì vẫn có thể implement trả về null.

Ví dụ đi kèm SDK là /APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java có thể xem online tại FragmentRetainInstance.

Nhưng chỉ headless không thì cũng không có ý nghĩa gì đặc biệt: đáng nói ở đây thông thường là kết hợp headless Fragment và setRetainInstance(true) còn gọi là retained headless Fragment.

Thực tế thì headless Fragment thường sẽ đi kèm với "retained" hay cũng sẽ là retained headless Fragment nên retained sẽ "vô tình" bị bỏ đi. Vì Fragment không có UI chỉ dùng với mục đích manage Object nào đó thường là background operation.

UI + setRetainInstance(true)

Việc Fragment có UI và sử dụng setRetainInstance(true) vẫn không có vấn đề gì dù trong trường hợp retained này có thể xảy ra một temporary memory leak.

Fragment có UI và developer cần thao tác với các UI. Việc thao tác thực hiện thông qua reference tới các UI/View dùng findViewById chẳng hạn. Với retained Fragment, các reference cũng được "retained" kéo theo View cũng sẽ được "retained". Do đó có thể gây temporary memory leak vì View internal giữ Context của Activity. Giả sử configuration change từ "portrait" sang "landscape". Fragment giữ Context của "portrait" Activity và 1 text box (View) cũng giữ Context của "portrait" Activity. Khi configuration change hoàn thành reference tới Activity sẽ được update lại, ví dụ trong onViewCreated, text box sẽ reference tới với "landscape" Activity. Tức trước khi việc này xảy ra, text box vẫn giữ reference của "portrait" Activity. Việc này có thể ngăn cản "portrait" Activity cũ được thu hồi bởi GC. Hiển nhiên, nếu không cẩn thận, ví dụ giữ một reference tới chính activity trong Fragment thì memory leak thật sự có thể xảy ra.

Đó là lý do "retained" mới là tính năng quan trọng hơn là headless. Headless chỉ là tính năng phụ kéo theo khi Fragment không cần xử lý UI.

Usage

Headless Fragment rất hữu dụng khi nó là "worker" Fragment khi nó giữ các object như Thread, AsyncTask, Socket, etc ...

Ví dụ thông thường là retain một AsyncTask qua configuration change sử dụng headless Fragment. Việc này sẽ bảo đảm rằng progress sẽ cập nhật và kết quả sẽ trả về cho currently displayed Activity instance (Activity đang hiển thị) và bảo đảm sẽ không bị leak leak AsyncTask khi configuration change.

Khi Activity tạo lại thì Fragment sẽ được hệ thống xác định theo ID hoặc tag (nếu không chỉ định cả hai hệ thống sẽ lấy theo ID của container view) để find/restore Fragment. Headless Fragment sẽ được thêm programmatically tức là thêm bằng code nên trong trường hợp của headless fragment unique identifier là tag. Đơn giản trong onCreate của Activity nếu không tìm thấy "retained" Fragment với findFragmentByTag thì  thực hiện add mới Fragment và start background operation. Reference tới Activity sẽ được update thông qua onAttach và onDetach.

Kết quả là headless Fragment có thể dùng để retain Presenter (MVP) do Presenter thường dùng long running background operation. Cách này cũng được Mosby 2 sử dụng.

Headless Fragment cũng có thể được sử dụng để tổ chức việc Ask runtime permission theo như bài viết trên Medium Use headless Fragment for Android M run-time permissions and to check network connectivity.

2016-11-28

Android evolution ... the right architecture?

Gần đây mình được hỏi sắp xếp, gợi ý giùm một cái simple architecture để phát triển Android app. Thật sự việc này rất là khó nhất là khi chỉ nói chung chung, và không nắm 1 team cụ thể nào.

Hiện tại việc phát triển ứng dụng Android hình như là thay đổi quá nhanh. Mỗi ngày đều có những libraries mới, tools mới (mình hay theo dõi trên Medium). Những lần dev Android apps từ những năm 2010 đến sau này gần giữa 2014 mình đều cố gắng bắt nhịp với sự phát triển của Android. Đợt này bỏ quá lâu, có quá nhiều thứ để tìm hiểu :(.

Khoảng trước năm 2011 việc dev có vẻ đơn giản, gọi Restful service, dùng SQLite, + disk cache, và có lẽ vậy là đủ, mọi thứ tổ chức sao cho clean nhất có thể. 2010, mình cũng có tham khảo Google I/O 2010 - Android REST client applications và tìm hiểu thêm. Thật ra presentation tại Google I/O chỉ trình bày hướng phát triển, cũng không chỉ rõ cách implementation. Một số code nói là demo lại thì chỉ được 1 phần, không take care hết các trường hợp như configuration changes, lifecycle, reference Activity ...

Từ RoboSpice đến Volley + EventBus

Khoảng 2010, trước RoboSpice, mình có thử dùng Droid-Fu (sau này chuyển thành Ignition), 1 dạng hack trực tiếp Activity lifecycle với việc sử dụng WeakReferences. Nói chung là cũng không thích lắm. Khoảng 2012 thì xuất hiện RoboSpice implement theo hướng sử dụng Service và cache request result, 1 hướng mà Google I/O đã đề cập. Nhưng code theo RoboSpice cũng khá là rườm rà, mỗi lần tạo request phải chỉ định cache key và các thứ liên quan, lúc đó RoboSpice cũng còn thay đổi khá nhiều mãi sau này mới hoàn chỉnh với addListenerIfPending() hay getDataFromCache(). Code cũng không sáng sủa là mấy khi Activity hay Fragment có quá nhiều việc để làm. Rất khó xác định cache bao lâu là vừa (với cách thực hiện hiển thị UI là lấy cache trước, request network sau "get cache first then request" ... sẽ đề cập sau). Việc xử lý rotation hay back với activity nhiều khi cũng không quá quan trọng. Lý do app nhiều khi chỉ để portrait mode và thật sự thì việc user nhấn back có vẻ bị "bi kịch hóa" hơi quá. RoboSpice có một cái theo mình rất dở là thích ôm quá nhiều thứ từ caching, OR mapping, parser, HTTP request... thành ra 1 đống khó kiểm soát.

Sau đó, 2013 thì Volley ra đời và cả EventBus cũng xuất hiện vào thời gian này. Mọi người chuyển sang xài Volley với suy nghĩ rằng nó là của Google :D, ko gọi là xài 3rd party library. Bên cạnh đó EventBus được xài để thực hiện loosely coupling. Lúc này mình cũng hay develop apps ko hỗ trợ rotation. Việc code thì đơn giản theo dạng Activity, Fragment nói chuyện với một dạng Repository thông qua EventBus, việc gọi Restful service được giao cho Volley. Tất nhiên dùng Volley và EventBus vẫn phải care vụ Acvitity leak về mặt kỹ thuật vẫn có thể xảy ra, nhưng về tổ chức code thì có vẻ clean hơn.

Tham khảo thêm bài viết Android Application Architecture của Iván Carballo hay
Robust and readable architecture for an Android app part 2 (part 1) của Joan Zapata.

https://github.com/JoanZapata/android-asyncservice/wiki






Một số tổng hợp về chủ đề này trên github của ziem

MVP, MVVM và Clean architect

Sau đó là đến thời gian mình tìm hiểu Clean Architecture của Robert C. Martin a.k.a Uncle Bob. Như Uncle Bob đã nói architecture là mục tiêu, ý định (intent) không phải là framework: "Architecture is about intent, not frameworks".

Một architecture tốt là một architecture cho phép một quyết định đột ngột có thể thực hiện một cách dễ dàng, bao gồm một số đặc điểm:
+ Dễ maintain: kể cả thêm/bớt tính năng, thay đổi yêu cầu (cả những tính năng chưa biết trước)
+ Dễ test
+ Decoupled (tạo từ những thành phần độc lập tương đối và liên kết yếu - independent and loosely-coupled components)

Khoảng thời điểm này có rất nhiều "architecture" từ những platform khác được áp dụng cho phát triển Android từ MVC, MVP tới MVVM. Thật ra nếu gọi architecture chung chung thì không đầy đủ, chỉ là cho phần tổ chức UI vì đây thực ra là những UI design patterns. Mình biết MVP, MVVM từ khi làm việc với .NET/C# (ASP.NET, WPF và Prism). Mình ko đi sâu chi tiết về các pattern này. Có thể tham khảo theo các bài viết của Martin Fowler (phân tích rất kỹ) về MVC, Passive View, Presentation Model và MVP.

MVVM (theo MSDN)


MVVM (được phát triển từ Microsoft) thông thường đi kèm với data bindings, commands không được ưa chuộng bằng MVP trên Android (có lẽ đa phần không thích binding Android Data Binding Library). 

Các project mẫu thực hiện MVP trên GitHub
https://github.com/googlesamples/android-architecture

https://github.com/googlesamples/android-architecture/tree/todo-mvp


Xem thêm series giới thiệu MVP của Tin Megali, Model View Presenter (MVP) in Android, bài viết MVP for Android: how to organize the presentation layer của Antonio Leiva.

Có thể kể đến các libraries thực hiện MVP architecture như Mosby, Nucleus... Theo ý kiến của mình thì ít nhất cũng phải xem qua các libraries này thực hiện MVP như thế nào. Nếu không thích tự mình viết MVP thì hoàn toàn có thể dùng luôn các library này. Một trong những tính năng được ưa thích của các libraries này là Presenter có thể survive qua configuration/orientation changes (sẽ đề cập kỹ phần sau).

Mosby là library đặt theo tên architect Ted Mosby trong series "How I met your mother", mình cũng thích series này :D. Hiện tại Mosby đã có version 3.0 SNAPSHOT. Mosby bao gồm 2 phần chính MVP và ViewState, một component nhỏ liên kết giữa Presenter và View để giải quyết orientation changes.

Tương tự Mosby Nucleus cũng có tính năng tự động re-attach background task vào view mới khi orientation changes.

Mục đích đầu tiên MVP là cho phép thực hiện test dễ dàng hơn (nhất là unit tests với mocking). Nhưng theo cá nhân mình suy nghĩ thì dùng MVP cũng nhiều overhead. Đầu tiên là phải khai báo và quản lý quá nhiều interface. Những tính năng thông thường như login đôi khi phải khai báo vài interface trong khi dev nào cũng có thể hình dung login với username/password còn những thứ khác cũng chỉ là thứ yếu? Việc cố gắng tách Presenter khỏi Android có thể cần rất nhiều mô tả về UI logic từ những thứ rất nhỏ như show/hide loading progress.

Đôi khi việc phát triển app trên Android cũng ko quá phức tạp mà đòi hỏi về thời gian phát triển nhiều hơn, vòng đời của app cũng khá ngắn → thông thường người dev Presenter cũng là người dev View, việc mô tả lại UI logic và implement cho Presenter là quá tốn công (lại khá chán). Nhiều khi mình thiên về sử dụng một dạng simple như mô tả của Joan Zapata ở trên.

Tất nhiên nếu sp có vòng đời lâu và testing thật sự quan trọng thì code theo MVP không có vấn đề gì. Còn nếu không quá cần thiết thì có thể bỏ qua trong một vài trường hợp. Với một developer cứng tay dù app đã viết theo cách nào (tất nhiên có tổ chức) thì khi join team, developer này cũng có thể nhanh chóng bắt kịp. Và rồi thì những công nghệ chúng ta đang dùng cũng sẽ trở thành dead technologies ngày nào đó giống như "good code today, legacy code tomorrow".

Theo như Robert C. Martin a.k.a Uncle Bob, Clean Code: A Handbook of Agile Software Craftsmanship

"Who can justify the expense of a sixlane highway through the middle of a small town that anticipates growth? Who would want such a road through their town?"

"Ai có thể biện minh/thuyết phục người khác về chi phí để làm một con đường cao tốc 6 làn xe qua một thị trấn nhỏ rằng sẽ dành cho việc tăng trưởng được tiên đoán trước. Ai muốn có một con đường như vậy đi qua thị trấn của họ".

Kết hợp với RxJava
Nếu sử dụng MVP thì có lẽ sẽ thiếu sót nếu bỏ qua RxJava. Có thể tham khảo việc kết hợp RxJava và MVP như architecture của Iván Carballo, gọi là "MVP-based architecture powered by RxJava".

MVP-based architecture
https://labs.ribot.co.uk/android-application-architecture-8b6e34acda65#.tgds3woxp

Lưu ý trong architecture này event bus chỉ dùng rất "hạn chế" không dùng cho những event chỉ liên quan đến một màn hình mà liên quan đến brocasting toàn bộ như user logged out event.

MVP presenter và Activity/Fragment lifecycle, view state

Việc thực hiện MVP có cả ngàn cách. Và thật sự thì MVP cũng chỉ là chỉ dẫn về architecture còn việc thực hiện ntn thì vẫn tùy thuộc vào implementation. Một câu hỏi quan trọng đặt ra là mối quan hệ giữa Presenter và Activity/Fragment lifecycle và view state ntn?

Theo định nghĩa của Presenter thì không có chức năng nào liên quan đến lifecycle và việc persistence view state. Đầu tiên hãy tham khảo bài viết Presenters don't need lifecycle events của Hannes Dorfmann. Tóm tắt như sau: tác giả thấy ko có lý do gì để Presenters có những callbacks liên quan đến Activity/Fragment lifecycle như onCreate(), onPause() và onResume()... 

Việc bắt Presenter quản lý luôn cả lifecycle của Android là không hợp lý. Tại sao lại focus vào những view phức tạp như Fragment? Và nếu đem "phức tạp" đó vào Presenter thì nó cũng sẽ thành 1 đám hỗn độn. Team SoundCloud đã tạo một library là LightCycle để "tách vấn đề này ra" nhưng nói chung "liên quan" Presenter chỉ là đứng riêng ra. Team của Trello thì tạo RxLifecycle tiếp cận lifecycle bằng RxJava. Theo Hannes Dorfmann walk-around là tách khỏi View những logic liên quan đến lifecycle bằng cách thêm 1 layer.

Có thể xem thêm một số thông tin ở các bài viết:
  1. Android MVP - Part 2: Presenters and view state của Nathan Zylbersztejn  
  2. Android code that scales, with MVP của Nathan Barraille.
  3. MVP - Presenters that survive configuration changes của Brad Campbell
Tóm lại vấn đề Presenter và lifecycle có thể liệt kê một số cách giải quyết như sau:
  1. Để Presenter die khi Activity/Fragment die và restore state thông qua onSaveInstanceState (liên quan đến vụ callbacks). Việc này có thể thực hiện với các data đơn giản (có thể serialization được, Parcelable với Bundle). Nhưng sẽ phức tạp khi Presenter reference tới các object khó thực hiện serialization hoặc reference tới một background operation.
  2. Dùng Presenter pool, một dạng static caching đâu đó. Nếu dùng cách này thì phải chú ý đến trường hợp có nhiều Fragment cùng loại, cần tổ chức key của Presenter. Static caching cũng ko được đẹp đẽ cho lắm nhất là khi số lượng Presenter khá nhiều.
  3. Một cách khác là dùng headless Fragment (nghe giống kị sĩ không đầu :D), tên do community gọi Fragment không có UI và gọi setRetainInstance(true). Cách này có thể dùng với Dagger để Inject Presenter với fragment scope.
  4. Sử dụng Loaders và cache.
  5. Sử dụng các libraries hỗ trợ tính năng này như Mosby, Nucleus.

Unidirectional UI pattern/data flow

OK, mình tiếp tục liệt kê thêm những lựa chọn khác.

Flux, "Flux is the application architecture that Facebook uses for building client-side web applications.", là kiến trúc được Facebook dùng để build ứng dụng web đi chung với React.
--- và ---
Redux, "Redux is an application architecture inspired by Facebook Flux and and functional programming language Elm.", một application architecture "lấy cảm hứng" từ Flux và Elm.

Cả hai architecture đều có mấu chốt là dòng dữ liệu 1 chiều unidirectional data flow và đều là architecture cho web application (rất hot với front-end development).

Để thực hiện theo Redux thì khó khăn đầu tiên là phải suy nghĩ "theo Redux". Redux muốn developer nghĩ rằng ứng dụng bắt đầu với initial state và sẽ tương tác với một dòng actions (đến đây thì sẽ thấy bóng dáng reactive và functional programming). Nó đóng vai trò một container chứa state của ứng dụng.

Tất nhiên khi nói tới Flux và Redux thì không thể không nhắc React vì các kiến trúc này rất phù hợp với cách hoạt động của React (cho phần View).

Giới thiệu qua đã đủ, bây giờ tập trung cho Anroid. Nếu đồng ý với nhận định Android app không quá phức tạp như back-end và có vẻ giống client-side web app hơn thì đầu tiên hãy đọc một bài rất hay của Luis G. Valle Flux Architecture on Android.
"Well you have to deal with platform issues: memory, storage, pause, resume, network, location, etc. But that is not your app business logic. You have all of that in every app."



Với kiến trúc này thì mọi thứ có vẻ gọn hơn với 4 phần như diagram, nhưng để ý lại thì nó tập trung phức tạp rất nhiều lên Store. Library hỗ trợ Flux trên Android khá ít, ngay cả Facebook khi mới đề xuất về Flux cũng không hướng dẫn cụ thể thực hiện ntn.

Tuy nhiên ý tưởng của Facebook được mọi người ủng hộ và cải tiến thành Redux. Trikita có implement một library gọi là Jedux xem bài viết Writing a Todo app with Redux on Android.

Dù với UI pattern nào thì các vấn đề cần giải quyết với Android cũng vẫn như cũ và cần phải tìm hiểu thêm. Chi tiết thực hiện app với Redux có lẽ sẽ bàn kỹ ở một bài khác.