<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="https://publishpress.com/"
	>

<channel>
	<title>Flutter Archives - Tomoshare</title>
	<atom:link href="https://blog.tomosia.com.vn/danh-muc/flutter/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.tomosia.com.vn/danh-muc/flutter/</link>
	<description>Kênh chia sẻ kiến thức Tomosia Việt Nam</description>
	<lastBuildDate>Wed, 27 Mar 2024 08:31:13 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://blog.tomosia.com.vn/wp-content/uploads/2023/09/cropped-icon-32x32.png</url>
	<title>Flutter Archives - Tomoshare</title>
	<link>https://blog.tomosia.com.vn/danh-muc/flutter/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Cách lắng nghe sự kiện vòng đời trong Flutter từ 3.13 trở đi.</title>
		<link>https://blog.tomosia.com.vn/cach-lang-nghe-su-kien-vong-doi-trong-flutter-tu-3-13-tro-di/</link>
					<comments>https://blog.tomosia.com.vn/cach-lang-nghe-su-kien-vong-doi-trong-flutter-tu-3-13-tro-di/#comments</comments>
		
		<dc:creator><![CDATA[admin_tomosia]]></dc:creator>
		<pubDate>Wed, 27 Mar 2024 08:31:09 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=3247</guid>

					<description><![CDATA[<p>Trong Flutter, có một số sự kiện trong vòng đời mà bạn có thể nghe để xử lý&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/cach-lang-nghe-su-kien-vong-doi-trong-flutter-tu-3-13-tro-di/">Cách lắng nghe sự kiện vòng đời trong Flutter từ 3.13 trở đi.</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="800" height="500" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-1.png" alt="" class="wp-image-3250" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-1.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-1-300x188.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-1-768x480.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-1-380x238.png 380w" sizes="(max-width: 800px) 100vw, 800px" /></figure>



<p>Trong Flutter, có một số sự kiện trong vòng đời mà bạn có thể nghe để xử lý các trạng thái khác nhau của ứng dụng, nhưng hôm nay, chúng ta sẽ thảo luận về các sự kiện didChangeAppLifecycleState trước và sau phiên bản Flutter 3.13.</p>



<h2 id="trang-thai-vong-doi-trong-ung-dung-flutter-la-gi" class="wp-block-heading"><strong>Trạng thái vòng đời trong ứng dụng Flutter là gì?</strong></h2>



<p>Sự kiện này được kích hoạt bất cứ khi nào trạng thái vòng đời của ứng dụng thay đổi. Các trạng thái có thể được tiếp tục, không hoạt động, tạm dừng, tách rời và ẩn. Bạn có thể nghe sự kiện này bằng cách sử dụng mixin WidgetsBindingObserver.</p>



<ul class="wp-block-list">
<li>resumed: Trên tất cả các nền tảng, trạng thái này cho biết ứng dụng đang ở trạng thái chế độ chạy mặc định cho một ứng dụng đang chạy có tiêu điểm đầu vào và dễ thấy.</li>



<li>detached: Ứng dụng này vẫn được lưu trữ bởi công cụ Flutter nhưng được tách ra khỏi bất kỳ lượt xem máy chủ nào.</li>



<li>inactive: Ít nhất một chế độ xem của ứng dụng được hiển thị nhưng không có chế độ xem nào có đầu vào tập trung. Ứng dụng vẫn chạy bình thường.</li>



<li>hidden: Tất cả các chế độ xem của một ứng dụng đều bị ẩn vì ứng dụng đó sắp bị tạm dừng (trên iOS và Android) hoặc vì nó đã bị thu nhỏ hoặc được đặt trên một màn hình không còn hiển thị (trên màn hình không có web) hoặc đang chạy trong một cửa sổ hoặc tab không còn hiển thị (trên web).</li>



<li>paused: Ứng dụng hiện không hiển thị với người dùng và không phản hồi tới đầu vào của người dùng.</li>
</ul>



<h2 id="lang-nghe-su-kien-vong-doi-truoc-phien-ban-3-13" class="wp-block-heading"><strong>Lắng nghe sự kiện vòng đời trước phiên bản 3.13</strong></h2>



<p>Trong các phiên bản Flutter trước 3.13, bạn có thể xử lý các sự kiện trong vòng đời ứng dụng bằng cách sử dụng mixin WidgetsBindingObserver. Để thực hiện việc này, bạn sẽ bao gồm mixin WidgetsBindingObserver trong lớp State của mình và ghi đè phương thức didChangeAppLifecycleState. Trong phương pháp này, bạn có thể truy cập trạng thái hiện tại của ứng dụng (AppLifecycleState) và phản hồi tương ứng với các sự kiện khác nhau trong vòng đời ứng dụng.</p>



<pre class="wp-block-code"><code>class AppLifeCycleExample extends StatefulWidget {
  const AppLifeCycleExample({super.key});

  @override
  State&lt;AppLifeCycleExample> createState() => _AppLifeCycleExampleState();
}

class _AppLifeCycleExampleState extends State&lt;AppLifeCycleExample>
    with WidgetsBindingObserver {
  late final AppLifecycleListener listener;

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.detached:
        _onDetached();
      case AppLifecycleState.resumed:
        _onResumed();
      case AppLifecycleState.inactive:
        _onInactive();
      case AppLifecycleState.hidden:
        _onHidden();
      case AppLifecycleState.paused:
        _onPaused();
    }

    super.didChangeDependencies();
  }

  void _onDetached() => print('detached');
  void _onResumed() => print('resumed');
  void _onInactive() => print('inactive');
  void _onHidden() => print('hidden');
  void _onPaused() => print('paused');

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}</code></pre>



<h2 id="lang-nghe-su-kien-vong-doi-sau-phien-ban-flutter-3-13" class="wp-block-heading">Lắng nghe sự kiện vòng đời sau phiên bản Flutter 3.13</h2>



<p>Sau Flutter 3.13, chúng ta có thể nghe các sự kiện trong vòng đời ứng dụng bằng cách sử dụng lớp AppLifecycleListener mới.</p>



<p>Lớp AppLifecycleListener cung cấp một cách tiếp cận thuận tiện và thay thế để nghe các sự kiện trong vòng đời ứng dụng trong Flutter. Thay vì trực tiếp sử dụng mixin WidgetsBindingObserver, bạn có thể sử dụng lớp AppLifecycleListener để đơn giản hóa quy trình.</p>



<p>Để sử dụng AppLifecycleListener, hãy tạo một phiên bản của lớp và chuyển các lệnh gọi lại sự kiện mong muốn mà bạn muốn nghe. Điều này cho phép bạn dễ dàng xử lý các sự kiện trong vòng đời ứng dụng cụ thể mà không cần triển khai toàn bộ mixin WidgetsBindingObserver.</p>



<p>Bằng cách sử dụng AppLifecycleListener, bạn có thể hợp lý hóa mã của mình và làm cho mã dễ đọc và dễ bảo trì hơn vì bạn chỉ cần tập trung vào các sự kiện cụ thể mà bạn quan tâm.</p>



<pre class="wp-block-code"><code>class AppLifeCycleExample extends StatefulWidget {
  const AppLifeCycleExample({super.key});

  @override
  State&lt;AppLifeCycleExample> createState() => _AppLifeCycleExampleState();
}

class _AppLifeCycleExampleState extends State&lt;AppLifeCycleExample> {
  late final AppLifecycleListener listener;

  @override
  void initState() {
    super.initState();
    listener = AppLifecycleListener(onStateChange: _onStateChanged);
  }

  void _onStateChanged(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.detached:
        _onDetached();
      case AppLifecycleState.resumed:
        _onResumed();
      case AppLifecycleState.inactive:
        _onInactive();
      case AppLifecycleState.hidden:
        _onHidden();
      case AppLifecycleState.paused:
        _onPaused();
    }
  }

  void _onDetached() => print('detached');
  void _onResumed() => print('resumed');
  void _onInactive() => print('inactive');
  void _onHidden() => print('hidden');
  void _onPaused() => print('paused');
  @override
  void dispose() {
    listener.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
</code></pre>



<h2 id="diem-khac-biet-giua-chung-la-gi" class="wp-block-heading">Điểm khác biệt giữa chúng là gì?</h2>



<p>Nhìn qua thì cách cũ và cách mới không có sự khác biệt. Để hiểu rõ hơn về lợi ích của AppLifecycleListener cùng xem sơ đồ vòng đời dưới đây. </p>



<figure class="wp-block-image size-full"><img decoding="async" width="1000" height="360" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/image.png" alt="" class="wp-image-3249" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image.png 1000w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-300x108.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-768x276.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-380x137.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/image-800x288.png 800w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure>



<p>Sơ đồ minh họa các trạng thái khác nhau của ứng dụng Flutter và các chuyển đổi có thể có giữa chúng. </p>



<p>Theo cách tiếp cận “cũ”, khi ghi đè phương thức didChangeAppLifecycleState, chúng ta chỉ có thể lắng nghe những thay đổi trạng thái cụ thể, chẳng hạn như khi ứng dụng chuyển sang trạng thái resumed. Tuy nhiên, bạn không thể nắm bắt được thông tin về sự chuyển đổi giữa các trạng thái. Ví dụ: bạn không thể xác định liệu ứng dụng đã chuyển sang trạng thái resumed từ inactive hay detached.</p>



<p>Với lớp AppLifecycleListener, giờ đây chúng ta có thể lắng nghe những chuyển đổi trạng thái này. Điều này có nghĩa là chúng ta có thể theo dõi cũng như phản hồi chuỗi trạng thái mà ứng dụng của bạn trải qua, hiểu biết toàn diện hơn về vòng đời của ứng dụng.</p>



<p>Bằng cách sử dụng lớp AppLifecycleListener, chúng ta có thể nắm bắt, xử lý hiệu quả quá trình chuyển đổi giữa các trạng thái, kiểm soát và tùy chỉnh hành vi ứng dụng một cách chính xác hơn.</p>



<h2 id="huy-viec-thoat-ung-dung-bang-lop-applifecyclelistener" class="wp-block-heading">Hủy việc thoát Ứng dụng bằng lớp AppLifecycleListener</h2>



<p>Trong lớp AppLifecyleListener có một lệnh gọi lại onExitRequested. Lệnh này được sử dụng để hỏi ứng dụng xem liệu nó có cho phép thoát khỏi ứng dụng trong trường hợp lệnh thoát có thể bị hủy hay không. </p>



<p>Ví dụ,chúng ta thể được sử dụng cho các ứng dụng MacOS, khi người dùng cố gắng đóng ứng dụng khi có những thay đổi chưa được lưu.</p>



<pre class="wp-block-code"><code>class AppLifeCycleExample extends StatefulWidget {
  const AppLifeCycleExample({super.key});

  @override
  State&lt;AppLifeCycleExample> createState() => _AppLifeCycleExampleState();
}

class _AppLifeCycleExampleState extends State&lt;AppLifeCycleExample> {
  late final AppLifecycleListener listener;

  @override
  void initState() {
    super.initState();
    listener = AppLifecycleListener(
      onStateChange: _onStateChanged,
      onExitRequested: _onExitRequested,
    );
  }

  void _onStateChanged(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.detached:
        _onDetached();
      case AppLifecycleState.resumed:
        _onResumed();
      case AppLifecycleState.inactive:
        _onInactive();
      case AppLifecycleState.hidden:
        _onHidden();
      case AppLifecycleState.paused:
        _onPaused();
    }
  }

  void _onDetached() => print('detached');
  void _onResumed() => print('resumed');
  void _onInactive() => print('inactive');
  void _onHidden() => print('hidden');
  void _onPaused() => print('paused');
  @override
  void dispose() {
    listener.dispose();
    super.dispose();
  }

  Future&lt;AppExitResponse> _onExitRequested() async {
    final response = await showDialog&lt;AppExitResponse>(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog.adaptive(
        title: const Text('Bạn có muốn thoát ứng dựng?'),
        content: const Text('Tất cả các tiến trình chưa được lưu sẽ bị mất.'),
        actions: &#91;
          TextButton(
            child: const Text('Huỷ'),
            onPressed: () {
              Navigator.of(context).pop(AppExitResponse.cancel);
            },
          ),
          TextButton(
            child: const Text('Đồng ý'),
            onPressed: () {
              Navigator.of(context).pop(AppExitResponse.exit);
            },
          ),
        ],
      ),
    );

    return response ?? AppExitResponse.exit;
  }

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
</code></pre>



<p>Cảm ơn mọi người đã đọc hết bài viết này!</p>
<p>The post <a href="https://blog.tomosia.com.vn/cach-lang-nghe-su-kien-vong-doi-trong-flutter-tu-3-13-tro-di/">Cách lắng nghe sự kiện vòng đời trong Flutter từ 3.13 trở đi.</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/cach-lang-nghe-su-kien-vong-doi-trong-flutter-tu-3-13-tro-di/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Fastlane Flutter App Distribution (part 1: Firebase app distribution iOS)</title>
		<link>https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/</link>
					<comments>https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/#comments</comments>
		
		<dc:creator><![CDATA[admin_tomosia]]></dc:creator>
		<pubDate>Tue, 05 Mar 2024 06:16:18 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=3196</guid>

					<description><![CDATA[<p>Trong bài viết này, tôi sẽ hướng dẫn cách thiết lập và triển khai ứng dụng Flutter của&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/">Fastlane Flutter App Distribution (part 1: Firebase app distribution iOS)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Trong bài viết này, tôi sẽ hướng dẫn cách thiết lập và triển khai ứng dụng Flutter của bạn và phân phối ứng dụng tự động với sự trợ giúp của fastlane.</p>



<p><strong>Fastlane là gì? </strong></p>



<p>Fastlane là một công cụ phân phối ứng dụng (CD) mã nguồn mở giúp tự động hóa việc xây dựng và triển khai ứng dụng Android và iOS lên Firebase,  Play Store hoặc App Store. Bạn có thể tùy chỉnh các lane theo môi trường DEV, STAGING, PROD theo ý muốn và chúng cũng giúp bạn tự động hóa quy trình ký mã cho cả hai nền tảng.</p>



<p>Cấu hình cho fastlane cho flutter thực chất là cấu hình riêng cho 2 môi trường iOS và Android trong project. Cấu hình riêng này hoàn toàn có thể dùng cho môi trường native iOS hoặc Android.<br><br>Tôi sẽ chia làm 3 phần<br>Phần 1: Config fastlane Firebase app distribution cho iOS<br>Phần 2: Config fastlane Firebase app distribution cho Android<br>Phần 3: Config fastlane Testflight iOS và Google Playstore Android<br><br>Hãy cùng bắt đầu với phần 1: Config fastlane Firebase app distribution cho iOS</p>



<p><strong>Bước 1: Cài đặt fastlane</strong></p>



<p>Bắt đầu cài đặt đơn giản qua Homebrew (macOS). Bạn có thể tham khảo tại tại <a href="https://docs.fastlane.tools/getting-started/ios/setup/">fastlane doc</a></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="brew install fastlane" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">brew install fastlane</span></span></code></pre></div>



<p><strong>Bước 2: Khởi tạo fastlane trong project iOS</strong></p>



<p>trỏ tới thư mục iOS trong dự án flutter của bạn thông qua terminal </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="cd ios" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">cd ios</span></span></code></pre></div>



<p>khởi tạo fastlane </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="fastlane init" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">fastlane init</span></span></code></pre></div>



<p>Lúc này sẽ có 4 lựa chọn để fastlane tạo sẵn cho bạn config mặc định. Để đơn giản hay bắt đầu với lựa chọn 4:  Manual setup </p>



<figure class="wp-block-image size-full"><img decoding="async" width="648" height="399" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25.png" alt="" class="wp-image-3199" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25.png 648w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25-300x185.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25-380x234.png 380w" sizes="(max-width: 648px) 100vw, 648px" /></figure>



<p>Tiếp tục và Enter cho đến khi hoàn thành<br><br>Lúc này kiểm tra trong thư mục ios sẽ sinh ra thu mục fastlane và các file config sẵn, chúng ta sẽ chú ý tới Fastfile, nơi config các lệnh tự động.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="398" height="383" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29.png" alt="" class="wp-image-3200" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29.png 398w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29-300x289.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29-380x366.png 380w" sizes="auto, (max-width: 398px) 100vw, 398px" /></figure>



<p>Mở file Fastfile bạn sẽ thấy config mặc định lane: custom_lane<br>chúng ta sẽ thêm 1 dòng printf(&#8220;Hello fastlane&#8221;) để test</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="630" height="399" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14.png" alt="" class="wp-image-3201" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14.png 630w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14-300x190.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14-380x241.png 380w" sizes="auto, (max-width: 630px) 100vw, 630px" /></figure>



<p>Giờ hãy cùng test qua terminal để xem fastlane nó chạy như nào:<br>chú ý vẫn tại thư mục ios trong project </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="fastlane custom_lane" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">fastlane custom_lane</span></span></code></pre></div>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="647" height="414" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01.png" alt="" class="wp-image-3202" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01.png 647w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01-300x192.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01-380x243.png 380w" sizes="auto, (max-width: 647px) 100vw, 647px" /></figure>



<p><em>Hello fastlane</em> đã được in ra  🎉</p>



<ul class="wp-block-list">
<li>Fastfile được viết bằng Ruby</li>
</ul>



<ul class="wp-block-list">
<li>lane: custom_lane nơi bạn gom chạy các task vụ chung lại với nhau. Bạn có thể tìm hiểu thêm tại <a href="https://docs.fastlane.tools/advanced/lanes/">doc</a><br><br>Giờ chúng ta cùng nhau config chi tiết lane: DEV mục tiêu là đẩy bản build môi trường DEV lên firebase app distribution.</li>
</ul>



<p><strong>Bước 3: Import certificate và provisioning profile </strong></p>



<ul class="wp-block-list">
<li>Để build được file ipa và đẩy lên firebase distribution cần certificate và provisioning được setup và cấu hình qua tài khoản apple developer. Trong bài viết tôi không đi sâu vào vấn đề tạo certificate cũng như provisioning tuy nhiên bạn có thể tìm hiểu thêm tại <a href="https://developer.apple.com/help/account/manage-profiles/create-a-development-provisioning-profile/">đây</a></li>



<li>Tới bước này mặc định bạn đã có certificate dạng dev_ios_distribution.p12 (với password) và provisioning dạng ios_app_name_dev_adhoc.mobileprovision cho môi trường DEV<br><br>Chúng ta có 2 cách<br>&#8211; Cách 1: là đưa lên git private mục đích bảo mật dành cho những thành viên có quyền mới truy cập vào build deploy được. Với cách này chúng ta dùng fastlane action match để get từ github private và import. Chi tiết tại <a href="https://docs.fastlane.tools/actions/match/#match">fastlane action match</a><br>&#8211; Cách 2: đưa thẳng file certificate và provisioning vào cùng thư mục để dễ dàng truy cập. Tôi sẽ đi sâu vào cách này ví nó đơn giản. <br>Tạo 2 thư mục certs và profile để save local cùng project ios</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="479" height="384" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31.png" alt="" class="wp-image-3204" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31.png 479w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31-300x241.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31-380x305.png 380w" sizes="auto, (max-width: 479px) 100vw, 479px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="596" height="386" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1.png" alt="" class="wp-image-3206" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1.png 596w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1-300x194.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1-380x246.png 380w" sizes="auto, (max-width: 596px) 100vw, 596px" /></figure>



<p><br>Bắt đầu config lane DEV trong Fastfile</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="655" height="423" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20.png" alt="" class="wp-image-3208" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20.png 655w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20-300x194.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20-380x245.png 380w" sizes="auto, (max-width: 655px) 100vw, 655px" /></figure>



<p>Thực hiện fastlane action <a href="https://docs.fastlane.tools/actions/import_certificate/#import_certificate">import_certificate</a> <br>&#8211;<strong>keychain_name</strong> :  kiểm tra xem certificate của bạn được import trong keychain nào, open Keychain Access trong Macos và tìm. Trong trường hợp của máy tôi, certificate <strong>iPhone Distribution xxx COMPANY LIMITTED (yyyyy) </strong>có trong keychain <strong>login</strong><br>&#8211;<strong>certificate_password</strong>: password <strong>certificate_distribution.p12 </strong>của bạn<br>&#8211;<strong>keychain_password</strong>: password vào keychain của bạn.<br><br>Thực hiện tiếp fastlane action <a href="https://docs.fastlane.tools/actions/install_provisioning_profile/#install_provisioning_profile">install_provisioning_profile</a><br>Trong trường hợp chạy máy ảo hoặc bạn muốn tách biệt bạn có thể dùng fastlane action <a href="https://docs.fastlane.tools/actions/create_keychain/#create_keychain">create_keychain</a> để tạo riêng keychain cho bạn.<br><br>Tiếp tục với action <a href="https://docs.fastlane.tools/actions/build_ios_app/#build_ios_app">build_ios_app</a> thực hiện export file ipa. Cần chú ý vài điểm sau<br>&#8211;<strong>scheme</strong> và <strong>configuration</strong>: đúng như bạn đã config trong xcode<br>&#8211;<strong>output_directory</strong>: đường dẫn thư mục chứ file ipa sau khi export thành công<br>&#8211;<strong>export_method</strong>: ad-hoc<br>&#8211;<strong>codesigning_identity</strong>: tên chính sác của provisioning profile<br>&#8211;<strong>provisionningProfile</strong>: chính xác tên bundle và tên file provisioning<br><br>Sau khi config xong test thử lệnh </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="fastlane DEV" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">fastlane </span><span style="color: #BF9EEE">DEV</span></span></code></pre></div>



<p>Vài lỗi có thể gặp phải:</p>



<ul class="wp-block-list">
<li>import certificate error: kiểm tra lại tên file và đường dẫn. Có thể lấy đường dẫn trực tiếp để test kỹ.</li>



<li>build_ios_app error: thêm debug: true để check kỹ lỗi gặp phải.</li>
</ul>



<p>Sau khi build thành công bạn sẽ thấy thư mục build/dev có file APP-DEV.ipa như hình</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="700" height="431" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21.png" alt="" class="wp-image-3209" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21.png 700w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21-300x185.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21-380x234.png 380w" sizes="auto, (max-width: 700px) 100vw, 700px" /></figure>



<p>Ngoài ra còn có file .dSYM.zip .File này mục đích để giải mã lỗi ngược dùng cho firebase crashlytics. Trường hợp bạn export không có file dSYM.zip hãy kiểm tra config trong Debug Information Format chọn DWARK with dSYM File.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1002" height="572" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25.png" alt="" class="wp-image-3210" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25.png 1002w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-300x171.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-768x438.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-380x217.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-800x457.png 800w" sizes="auto, (max-width: 1002px) 100vw, 1002px" /></figure>



<p><strong>4. Cấu hình upload Firebase distribution</strong><br>Giả định bạn đã setup thành công firebase và kết nối với project flutter. Chi tiết config firebase bạn có thể xem tại <a href="https://firebase.google.com/docs/flutter/setup?platform=ios">đây</a>. Chúng ta sẽ tiếp cụ với: </p>



<ul class="wp-block-list">
<li>Tìm App ID thông qua Firebase Console</li>



<li>Để phân phối ứng dụng thông qua Fastlane, bạn cần một <a href="https://firebase.google.com/docs/cli#install-cli-mac-linux">Firebase CLI token</a>. Chạy lệnh sau trong terminal để đăng nhập và tạo token:</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="firebase login:ci" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">firebase login</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4">ci</span></span></code></pre></div>



<p>Sau khi đăng nhập thành công, bạn sẽ nhận được một token. Hãy lưu trữ token này một cách an toàn.</p>



<p>Tiếp tục mở Fastfile và thêm config fastlane action <a href="https://docs.fastlane.tools/getting-started/ios/beta-deployment/#beta-testing-services">firebase_app_distriution</a> và <a href="https://docs.fastlane.tools/actions/upload_symbols_to_crashlytics/#upload_symbols_to_crashlytics">upload_symbols_to_crashlytics</a> </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="792" height="689" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04.png" alt="" class="wp-image-3211" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04.png 792w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04-300x261.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04-768x668.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04-380x331.png 380w" sizes="auto, (max-width: 792px) 100vw, 792px" /></figure>



<p>&#8211;<strong>groups</strong>: tên group test config trong firebase app distribution<br>&#8211;<strong>testers</strong>: list email chỉ định tới các tester đã add trong firebase app distribution<br>&#8211;<strong>upload_symbols_to_crashlytics</strong>: nếu bạn không sử dụng Firebase crashlytics hãy bỏ nó đi. <br><br>Các lỗi gặp phải:<br>-sai đường dẫn hoặc tên file ipa<br>-hãy đảm bảo bạn đã kích hoạt app distribution của ios trên firebase console<br>-đảm bảo version name, version code khác nhau mỗi lần upload.<br><br>Tới đây bạn đã hoàn thành việc config fastlane build Firebase app distribution cho môi trường iOS. <br>Hãy cùng đón đợi tiếp phần 2 config cho môi trường Android.</p>
<p>The post <a href="https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/">Fastlane Flutter App Distribution (part 1: Firebase app distribution iOS)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Một vài ví dụ về animation trong Flutter</title>
		<link>https://blog.tomosia.com.vn/mot-vai-vi-du-ve-animation-trong-flutter/</link>
					<comments>https://blog.tomosia.com.vn/mot-vai-vi-du-ve-animation-trong-flutter/#comments</comments>
		
		<dc:creator><![CDATA[linh chau]]></dc:creator>
		<pubDate>Tue, 02 Jan 2024 14:20:06 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=2703</guid>

					<description><![CDATA[<p>Như bạn biết, Animation trong các ứng dụng moblie giúp tăng tính trực quan, tính tương tác cho&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/mot-vai-vi-du-ve-animation-trong-flutter/">Một vài ví dụ về animation trong Flutter</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Như bạn biết, Animation trong các ứng dụng moblie giúp tăng tính trực quan, tính tương tác cho giao diện người dùng. Animation khi được sử dụng đúng cách, có thể tạo ra sự khác biệt lớn trong cách người dùng cảm nhận ứng dụng của bạn.</p>



<p>Với Flutter các animation được tạo bằng thư viện Animation. Chúng ta bắt đầu tìm hiểu thôi nào.</p>



<p>Chúng ta sẽ bắt đầu với 1 animation đơn giản nhất </p>



<p class="has-x-large-font-size"><strong>Rotation animation</strong></p>



<p>Bây giờ ta sẽ tạo 1 file tên là <strong>animate_widget.dart</strong> với nội dung như sau:</p>



<pre class="wp-block-code"><code>class AnimatedWidget extends StatefulWidget {
  const AnimatedWidget({super.key});

  @override
  State&lt;AnimatedWidget&gt; createState() =&gt; _AnimatedWidgetState();
}

class _AnimatedWidgetState extends State&lt;AnimatedWidget&gt;
    with TickerProviderStateMixin {
  late Animation _arrowAnimation;
  late AnimationController _arrowAnimationController;

  @override
  void initState() {
    super.initState();
    _arrowAnimationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    )..repeat();
    _arrowAnimation =
        Tween(begin: 0.0, end: pi).animate(_arrowAnimationController);
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _arrowAnimationController,
        builder: (context, child) =&gt; Transform.rotate(
          angle: _arrowAnimation.value,
          child: Image.asset("assets/image.jpg"),
        ),
      ),
    );
  }
}
</code></pre>



<p>Trong phương thức&nbsp;<strong>initState()</strong>,&nbsp;<strong>_arrowAnimationControll</strong>&nbsp;được khởi tạo với thời gian xảy ra của animation là 300 mili giây.</p>



<p><strong>_arrowAnimation</strong>&nbsp;được khởi tạo với giá trị begin là 0,0 và giá trị end là <strong>π </strong>do đó, nó sẽ xoay 180 độ. (pi = π = 180°)</p>



<p>Trong đoạn code trên chúng ta tạo 1 image và thêm một animation cho nó. Để image có animation thì cần phải bọc nó trong 1 <strong>AnimatedBuilder widget</strong> và dùng thuộc tính Transform.rotate để có thể tạo 1 vòng tròn với góc là giá trị của <strong>_arrowAnimation</strong>.</p>



<p><strong>AnimatedBuilder widget </strong>là một widget rất hữu ích khi xây dựng animation. Nó hiệu quả hơn việc gọi <strong>setState()</strong> mỗi khi có sự thay đổi giá trị của animation.</p>



<p>Ở đây mình có sử dụng hàm <strong>repeat()</strong> để có thể lặp đi lặp lại việc xoay tròn widget</p>



<p>Và để có thể sử dụng được animation thì bắt buộc phải kế thừa class <strong>TickerProviderStateMixin</strong> bằng cách thêm thuộc tính <strong>with TickerProviderStateMixin </strong></p>



<p>Và cũng đừng quên dùng phương thức  <strong>_arrowAnimationController.dispose()</strong> trong hàm <strong>dispose</strong>, nếu không thì sẽ bị memory leak đấy.</p>



<p class="has-x-large-font-size"><strong>Scale Up, Down Animation</strong></p>



<p>Chúng ta sửa lại 1 chút ở file <strong>animate_widget.dart</strong> ở trên </p>



<pre class="wp-block-code"><code>class AnimatedWidget extends StatefulWidget {
  const AnimatedWidget({super.key});

  @override
  State&lt;AnimatedWidget&gt; createState() =&gt; _AnimatedWidgetState();
}

class _AnimatedWidgetState extends State&lt;AnimatedWidget&gt;
    with TickerProviderStateMixin {
  late Animation _heartAnimation;
  late AnimationController _heartAnimationController;

  @override
  void initState() {
    super.initState();
      _heartAnimationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
    _heartAnimation = Tween(begin: 160.0, end: 200.0).animate(
      CurvedAnimation(
        curve: Curves.bounceOut,
        parent: _heartAnimationController,
      ),
    );

    _heartAnimationController.addStatusListener((AnimationStatus status) {
      if (status == AnimationStatus.completed) {
        _heartAnimationController.repeat();
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(children: &#91;
        const SizedBox(height: 100),
        Center(
          child: AnimatedBuilder(
            animation: _heartAnimationController,
            builder: (context, child) {
              return Center(
                child: SizedBox(
                  child: Center(
                    child: Icon(
                      Icons.favorite,
                      color: Colors.red,
                      size: _heartAnimation.value,
                    ),
                  ),
                ),
              );
            },
          ),
        ),
        const Spacer(),
        InkWell(
          onTap: () {
            _heartAnimationController.forward();
          },
          child: Container(
            color: Colors.red,
            margin: const EdgeInsets.only(bottom: 30),
            padding: const EdgeInsets.all(20),
            child: const Text("Run Example"),
          ),
        )
      ]),
    );
  }
}
</code></pre>



<p>Ta thêm hai biến&nbsp;<strong>_heartAnimation</strong>&nbsp;và&nbsp;<strong>_heartAnimationController</strong>&nbsp;tương ứng với Animation và AnimationController .</p>



<p><strong>_heartAnimationController</strong> sẽ được khởi tạo với duration là 1200 mili giây.</p>



<p><strong>_heartAnimation</strong> với begin là 150 và end là 170. Tương ứng với kích thước bé nhất và lớn nhất của trái tim.</p>



<p><img decoding="async" src="https://twemoji.maxcdn.com/v/14.0.2/72x72/2764.png" alt="❤️"></p>



<p>Giờ thì ta cần ghép icon trái tim với animation ở trên.</p>



<p>Và chúng ta có kết quả chạy như ở dưới đây</p>



<figure class="wp-block-video"><video height="1648" style="aspect-ratio: 762 / 1648;" width="762" controls src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screen-Recording-2023-12-25-at-2.48.59-PM.mov"></video></figure>



<p>Qua bài này mình đã giới thiệu các animation đơn giản và được sử dụng nhiều trong Flutter. Cảm ơn các bạn đã theo dõi.</p>
<p>The post <a href="https://blog.tomosia.com.vn/mot-vai-vi-du-ve-animation-trong-flutter/">Một vài ví dụ về animation trong Flutter</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/mot-vai-vi-du-ve-animation-trong-flutter/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		<enclosure url="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screen-Recording-2023-12-25-at-2.48.59-PM.mov" length="1015134" type="video/quicktime" />

			</item>
		<item>
		<title>Đa luồng trong Flutter </title>
		<link>https://blog.tomosia.com.vn/da-luong-trong-flutter/</link>
					<comments>https://blog.tomosia.com.vn/da-luong-trong-flutter/#comments</comments>
		
		<dc:creator><![CDATA[linh chau]]></dc:creator>
		<pubDate>Mon, 11 Dec 2023 12:20:22 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=2457</guid>

					<description><![CDATA[<p>Trước khi vào bài viết thì mình xin giới thiệu 1 chút về Flutter: Flutter là Mobile UI&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/da-luong-trong-flutter/">Đa luồng trong Flutter </a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p></p>



<p>Trước khi vào bài viết thì mình xin giới thiệu 1 chút về <strong>Flutter</strong>:</p>



<p>Flutter là Mobile UI Framework được Google phát hành vào năm 2017, được sử dụng để phát triền giao diện các ứng dụng di động trên các nền tảng Android/ iOS/ MacOs/……</p>



<p>Bất kỳ ngôn ngữ lập trình nào thì cũng phải tìm cách xử lý khi chạy những task nặng, tốn thời gian…Thì khi đó việc lập trình bất đồng bộ hoặc xử lý chúng trong background được tạo ra, thay vì xử lý trên Main thread thì việc gây giật UI hoặc ứng dụng chạy chậm, gây nóng máy làm khó chịu cho người sử dụng.</p>



<p>Và cũng là một UI Framework, Flutter đã cung cấp Stream và Future để hỗ trợ chúng ta trong việc giải quyết các vấn đề trong lập trình bất đồng bộ</p>



<ol class="wp-block-list">
<li class="has-large-font-size"><strong>Stream:&nbsp;</strong></li>
</ol>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="536" height="358" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/1.png" alt="" class="wp-image-2462" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/1.png 536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/1-300x200.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/1-380x254.png 380w" sizes="auto, (max-width: 536px) 100vw, 536px" /></figure>



<ol class="wp-block-list">
<li>Thực chất <strong>Stream</strong> là một luồng data bất đồng bộ, nó giống như 1 cái ống gồm 1 đầu nhận dữ liệu và đầu kia sẽ là dữ liệu khi nó xử lý xong</li>



<li>Cấu trúc của 1 <strong>Stream</strong>:&nbsp;</li>
</ol>



<ul class="wp-block-list">
<li>Để handle một Stream thì ta có <strong>StreamController</strong>.</li>
</ul>



<ul class="wp-block-list">
<li>Để đẩy dữ liệu vào <strong>Stream</strong> thì thông qua thuộc tính <strong>sink</strong>(có thể đẩy bất kỳ dữ liệu nào vào Stream: value, object, collection….)</li>
</ul>



<ul class="wp-block-list">
<li>Để <strong>publish</strong> dữ liệu ra ngoài thì chúng ta dùng thuộc tính <strong>stream</strong>,&nbsp; và dùng hàm <strong>listen</strong> để lắng nghe dữ liệu trả về</li>
</ul>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-secondary-color">Như ví dụ dưới đây, chúng ta sẽ tạo 1 danh sách có 10 phần tử, sau mỗi lần chạy thì sẽ add dữ liệu đó vào Stream.</mark></p>



<pre class="wp-block-code"><code>class _StreamWidgetExampleState extends State&lt;StreamWidgetExample&gt; {
 final StreamController _counterController = StreamController&lt;int&gt;();
 Stream get _counterStream =&gt; _counterController.stream;
 late StreamSubscription _subscription;

  _increment() {
    List.generate(10, (data) {
      _counterController.sink.add(data);
    });
  }

  @override
  void initState() {
    super.initState();
    _increment();
    _subscription = _counterStream.listen((data) {
      print("Receive data $data");
    });
  }

  @override
  void dispose() {
    _subscription.cancel();
    _counterController.close();
    super.dispose();
  }
}</code></pre>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="378" height="362" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-11-at-8.25.40-AM.png" alt="" class="wp-image-2496" style="width:230px;height:auto" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-11-at-8.25.40-AM.png 378w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-11-at-8.25.40-AM-300x287.png 300w" sizes="auto, (max-width: 378px) 100vw, 378px" /></figure>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-secondary-color">và đây là kết quả khi chạy function trên</mark></p>



<p class="has-large-font-size">2. <strong>Future</strong></p>



<ul class="wp-block-list">
<li><strong>Future </strong>là một hàm thực thi cho các hoạt động bất đồng bộ, và có 2 trạng thái đó là <strong>Completed </strong>và<strong> UnCompleted</strong></li>
</ul>



<ul class="wp-block-list">
<li><strong>Uncompleted</strong>: khi bạn gọi một hàm không đồng bộ, nó trả về một uncompleted future. Future này đang chờ cho các hoạt động không đồng bộ của hàm kết thúc hoặc trả về một error.</li>



<li><strong>Completed</strong>: nếu một hành động không đồng bộ thành công, future sẽ hoàn thành với một giá trị. Nếu không nó hoàn thành với một error.<ul><li>Hoàn thành với một giá trị: một future của kiểu <strong>Future&lt;T&gt;</strong> hoàn thành với một giá trị thuộc kiểu <strong>T</strong>. Ví dụ, một future với kiểu <strong>Future&lt;String&gt;</strong> thì nó sẽ trả về một giá trị string. Nếu một future không cung cấp một kiểu giá trị nào thì kiểu của future là <strong>Future&lt;void&gt;</strong>.</li></ul>
<ul class="wp-block-list">
<li>Hoàn thành với một error: nếu một hoạt động bất đồng bộ được thực hiện bởi một hàm thất bại vì bất kỳ lý do gì thì future hoàn thành với một error.</li>
</ul>
</li>



<li>Khi sử dụng hàm Future thì bắt buộc phải có từ khoá <strong>async</strong> và <strong>await:</strong></li>
</ul>



<p>Khi tạo 1 hàm bất đồng bộ thì ta phải thêm từ khoá <strong>async</strong> trước thân hàm:</p>



<ul class="wp-block-list">
<li><strong>async</strong>: Bạn có thể sử dụng từ khóa&nbsp;async&nbsp;trước thân hàm bất đồng bộ.</li>
</ul>



<ul class="wp-block-list">
<li><strong>async function</strong>: là một function được đánh dấu bởi từ khóa&nbsp;async.</li>
</ul>



<ul class="wp-block-list">
<li><strong>await</strong>: bạn có thể sử dụng từ khóa&nbsp;await&nbsp;để lấy kết quả từ một việc bất đồng bộ. Từ khóa&nbsp;await&nbsp;chỉ được sử dụng với hàm&nbsp;async.</li>
</ul>



<pre class="wp-block-code"><code> futureExampleFunc() async {
    //do something
 }</code></pre>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-secondary-color">Đây là hàm không có kiểu dữ liệu trả về</mark></p>



<pre class="wp-block-code"><code>  Future&lt;Auth&gt; futureFunc() async {
    //do something
    return Auth();
  }</code></pre>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-secondary-color">Còn đây là hàm có kiểu dữ liệu trả về</mark></p>



<pre class="wp-block-code"><code>
  initData() async {
    var auth = await futureFunc();
    print(auth);
  }</code></pre>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-secondary-color">Bây giờ chỉ cần thêm await khi chờ hàm này thực thi xong thì sẽ gán dữ liệu cho auth</mark></p>



<p>Đây là sơ đồ hoạt động của 1 <strong>Async Function</strong> </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="382" height="442" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/dart-async-function.png" alt="" class="wp-image-2501" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/dart-async-function.png 382w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/dart-async-function-259x300.png 259w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/dart-async-function-380x440.png 380w" sizes="auto, (max-width: 382px) 100vw, 382px" /></figure>



<p>Mặc dù Stream và Future đã hỗ trợ rất tốt trong việc lập trình bất đồng bộ, nhưng việc này vẫn chưa giải quyết được triệt để vấn đề vì chúng đều thực hiện trên luồng chính. Vì vậy Isolate được đưa ra như là một giải pháp cho việc này, bằng việc thực hiện các việc trên dưới background. </p>



<p class="has-large-font-size">3. <strong>Isolate</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="931" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-1024x931.png" alt="" class="wp-image-2505" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-1024x931.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-300x273.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-768x698.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-1536x1397.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-380x346.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-800x727.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16-1160x1055.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-16.png 1686w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="has-text-align-center">Sơ đồ hoạt động của Isolate</p>



<p><strong>Isolate</strong> là một tham chiếu đến một thread, thường khác với main thread hiện tại. Hay có thể nói dễ hiểu hơn thì Isolate là một phiên bản tương ứng của Thread trên ngôn ngữ lập trình Dart. Nó tương tự với Vòng lặp sự kiện <strong>(Event Loop)</strong> nhưng có một số điểm khác biệt như sau:</p>



<ul class="wp-block-list">
<li>Nó là 1 thread với bộ nhớ riêng, biệt lập.</li>



<li>Nó không thể chia sẻ trực tiếp dữ liệu với các thread khác.</li>



<li>Bất kể dữ liệu nào được truyền giữa các thread đều bị trùng lặp.</li>
</ul>



<p>Mỗi Isolate có một vòng lặp sự kiện <strong>(Event Loop)</strong> của riêng mình nhờ đó chúng sẽ hoạt động song song và độc lập với nhau.</p>



<p>Hãy xem ví dụ dưới đây để có thể hiểu rõ hơn cách hoạt động của 1 Isolate. Chúng ta tạo ra 1 Isolate để xử lý cho việc chạy 1 vòng lặp từ 1 -&gt; 1000000, sau khi chạy xong vòng lặp sẽ kết thúc và trả dữ liệu về cho hàm gọi nó. </p>



<pre class="wp-block-code"><code>void myIsolate(SendPort sendPort) {
  int data = 0;
  for (int i = 0; i &lt;= 1000000; i++) {
    data += i;
  // sau mỗi vòng lặp thì data sẽ cộng thêm giá trị của biến i
  }
  //sau khi kết thúc vòng lặp sẽ gọi hàm exit để kết thúc isolate này và trả data về
  Isolate.exit(sendPort, data);
}</code></pre>



<pre class="wp-block-code"><code>  void _runMyIsolate() async {
    var receivePort = ReceivePort();
  // đăng ký một isolate thông qua hàm spawn, ở đây ta sẽ truyền hàm xử lý dữ liệu và 1 port
    await Isolate.spawn(myIsolate, receivePort.sendPort);
    print(await receivePort.first);
  }</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="286" height="115" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-11-at-9.38.42-AM.png" alt="" class="wp-image-2514"/></figure>



<p>Chúng ta có thể thấy khi thực hiện việc tạo 1 thread isolate là thông qua method <strong>Isolate.spawn</strong> thì lúc này ứng dụng sẽ tạo ra 1 thread với tên là <strong>myIsolate</strong> chạy song song với luồng main, và luồng này sẽ bị đóng lại khi sử dụng method <strong>Isolate.exit.</strong></p>



<p class="has-large-font-size">4. Kết luận</p>



<p>Flutter ( Dart ) là Single-Thread , do đó để làm hài lòng người dùng, các nhà phát triển phải đảm bảo rằng ứng dụng sẽ chạy trơn tru nhất có thể. <strong>Stream</strong>, <strong>Futures</strong> và <strong>Isolates</strong> là những công cụ rất mạnh có thể giúp bạn đạt được mục tiêu này.</p>
<p>The post <a href="https://blog.tomosia.com.vn/da-luong-trong-flutter/">Đa luồng trong Flutter </a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/da-luong-trong-flutter/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>SwiftUI vs Flutter : Cách xây dựng UI</title>
		<link>https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/</link>
					<comments>https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/#comments</comments>
		
		<dc:creator><![CDATA[Le Quoc]]></dc:creator>
		<pubDate>Sun, 08 Oct 2023 08:30:34 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=934</guid>

					<description><![CDATA[<p>Giới thiệu Vào đầu năm 2019, Flutter phiên bản 1.0 đã được phát hành chính thức. Còn SwiftUI&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/">SwiftUI vs Flutter : Cách xây dựng UI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="427" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-1024x427.png" alt="" class="wp-image-935" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-1024x427.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-300x125.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-768x320.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-380x158.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-800x333.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-1160x483.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0.png 1200w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="gioi-thieu" class="wp-block-heading">Giới thiệu</h2>



<p>Vào đầu năm 2019, Flutter phiên bản 1.0 đã được phát hành chính thức. Còn SwiftUI cũng được giới thiệu bởi Apple vào năm 2019, Apple nói rằng SwiftUI là tương lai của việc phát triển ứng dụng trên các nền tảng của Apple (vì nó là đa nền tảng, cho phép bạn phát triển ứng dụng iOS, MacOS, WatchOS và tvOS chỉ bằng một mã nguồn duy nhất).</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Thay vì nói lí thuyết, ta sẽ thử viết 1 màn hình để play video. Xuyên suốt quá trình dựng UI, chúng ta sẽ cùng code và phân tích ưu điểm của mỗi framework nhé.</p>
</blockquote>



<h2 id="1-ui-tong-quan" class="wp-block-heading">1/ UI tổng quan</h2>



<p><strong>Flutter</strong> trước nhé.</p>



<p><strong>TopView</strong> class sẽ hiển thị tabs, ta sẽ tạo class <strong>HomeView</strong>. Ở <strong>HomeView</strong>, ta dùng widget <strong>CupertinoPageScaffold</strong>. Sẽ có <strong>navi</strong>, và 1 <strong>SingleChildScrollView</strong> để đảm bảo tính đầy đủ dữ liệu ở các thiết bị màn hình nhỏ.</p>



<p><strong>TopView</strong> và <strong>HomeView</strong> kế thừa từ StatefulWidget.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="875" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1024x875.png" alt="" class="wp-image-936" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1024x875.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-300x256.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-768x656.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1536x1312.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-380x325.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-800x683.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1160x991.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1.png 1702w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="845" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1024x845.png" alt="" class="wp-image-938" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1024x845.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-300x248.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-768x634.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1536x1268.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-380x314.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-800x660.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1160x958.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1.png 1696w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Rồi, đến <strong>SwiftUI</strong>, cũng sẽ hiển thị tabs tương tự</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="692" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-1024x692.png" alt="" class="wp-image-941" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-1024x692.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-300x203.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-768x519.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-380x257.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-800x541.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-1160x784.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1.png 1438w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>HomeScreen</strong> struct sẽ hiển thị video player và danh sách video.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="499" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1024x499.png" alt="" class="wp-image-939" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1024x499.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-300x146.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-768x375.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1536x749.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-2048x999.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-380x185.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-800x390.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1160x566.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3.png 2116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="2-ui-ve-video-player" class="wp-block-heading"><strong>2/ UI về video player</strong></h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="632" height="1024" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-632x1024.webp" alt="" class="wp-image-942" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-632x1024.webp 632w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-185x300.webp 185w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-380x615.webp 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4.webp 704w" sizes="auto, (max-width: 632px) 100vw, 632px" /></figure>



<p>Về phía <strong>Flutter</strong>, mỗi UI element được đặt trong 1 vị trí thích hợp bằng cách set <strong>MainAxisAlignment</strong> của <strong>Column</strong> widget và <strong>Row</strong> widget. <strong>CupertinoSlider</strong> widget sẽ đảm nhận việc hiển thị thanh slider. </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1014" height="1024" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-1014x1024.png" alt="" class="wp-image-943" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-1014x1024.png 1014w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-297x300.png 297w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-150x150.png 150w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-768x776.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-80x80.png 80w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-380x384.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-800x808.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-1160x1171.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6.png 1428w" sizes="auto, (max-width: 1014px) 100vw, 1014px" /></figure>



<p>Đối với <strong>SwiftUI</strong>, <strong>BigMovieCellOverlay</strong> struct sẽ đảm nhận việc hiển thị player thông qua <strong>VStack</strong> và <strong>HStack</strong>. <strong>Rectangle</strong> được bọc trong <strong>ZStack</strong> sẽ đóng vai trò <strong>Slider</strong> progress của video. </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="822" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1024x822.png" alt="" class="wp-image-944" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1024x822.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-300x241.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-768x617.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1536x1234.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-380x305.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-800x643.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1160x932.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7.png 1880w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="3-ui-ve-list" class="wp-block-heading">3/ UI về List</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="539" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-1024x539.png" alt="" class="wp-image-945" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-1024x539.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-300x158.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-768x404.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-380x200.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-800x421.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-1160x610.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8.png 1266w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="a-scroll-ngang" class="wp-block-heading">a/ Scroll ngang</h2>



<p>Về phía <strong>Flutter</strong>, Widget <strong>ListView</strong> trong <strong>Broadcasting</strong> được set <strong>Axis.horizontal</strong>, được xếp chồng bằng cách sử dụng <strong>Stack</strong>. Mỗi thumbnail được triển khai trong lớp <strong>BigPicCell</strong> được mô tả bên dưới.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="481" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-1024x481.png" alt="" class="wp-image-946" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-1024x481.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-300x141.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-768x361.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-380x179.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-800x376.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-1160x545.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9.png 1226w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Về phía <strong>SwiftUI</strong>, mỗi thumbnail được triển khai ở BigPicCell struct.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="528" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1024x528.png" alt="" class="wp-image-947" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1024x528.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-300x155.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-768x396.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1536x792.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-2048x1056.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-380x196.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-800x413.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1160x598.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10.png 2110w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="b-decoration" class="wp-block-heading">b/ Decoration</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="542" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-1024x542.png" alt="" class="wp-image-949" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-1024x542.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-300x159.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-768x406.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-380x201.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-800x423.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-1160x614.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11.png 1278w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Flutter</strong>, <strong>League</strong> class gần giống với <strong>Broadcasting</strong> class. Mỗi thumbnail được triển khai ở class bên dưới.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="723" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-1024x723.png" alt="" class="wp-image-950" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-1024x723.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-300x212.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-768x542.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-200x140.png 200w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-380x268.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-800x565.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12.png 1116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>BigPicCell</strong> class sẽ đảm nhận việc hiển thị thumbnails và thông tin, được bọc bởi 1 <strong>Column</strong> widget</p>



<p>Thumbnail dùng <strong>BoxDecoration</strong> widget tạo cái viền vàng xung quanh.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="437" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1024x437.png" alt="" class="wp-image-951" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1024x437.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-300x128.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-768x328.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1536x656.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-380x162.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-800x342.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1160x495.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1.png 1836w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Tương tự thì <strong>SwiftUI</strong> cũng có <strong>League</strong> và <strong>BigPicCell</strong><em> </em>struct sẽ đảm nhận việc hiển thị thumbnails và thông tin.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="703" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1024x703.png" alt="" class="wp-image-954" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1024x703.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-300x206.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-768x527.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1536x1054.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-2048x1405.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-380x261.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-800x549.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1160x796.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1.png 2116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>ZStack</strong> set <code>border(Color.yellow, width: isWatchingId == id ? 2 : 0)</code> để hiển thị viền vàng xung quanh. Khi tap vào, video sẽ được play và <strong>Text(“ Live”)</strong> sẽ được hiển thị ở thumbnail. </p>



<p><code>Group {isRecord ? nil : LiveLabel()}<br>.padding(EdgeInsets.init(top: 15.0, leading: 15.0, bottom: 0.0, trailing: 0.0)).</code></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="770" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1024x770.png" alt="" class="wp-image-952" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1024x770.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-300x225.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-768x578.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1536x1155.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-380x286.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-800x602.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1160x872.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15.png 1944w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="4-summary" class="wp-block-heading">4/Summary:</h2>



<p>Đã trải qua quãng thời gian làm việc khá lâu với SwiftUI, trước đó là UIKit và Interfacebuilder của iOS. Bằng so sánh chủ quan của mình, tôi đánh giá cao về sự hiện đại trong cách Flutter framework hỗ trợ chúng ta dựng UI.</p>



<p>Còn về phía bạn, hãy chia sẽ cảm nghĩ của mình ở phần bình luận nhé.</p>
<p>The post <a href="https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/">SwiftUI vs Flutter : Cách xây dựng UI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>
