Technology for Business

สรุปเนื้อหาเรื่อง Google Flutter และ All Cross Platform จากงาน Mobile Conf 2018

เรื่องที่เกี่ยวข้อง - Cross Platform, Google Flutter, Ionic Framework, Mobile Conf, React Native, Speaker, Xamarin

จบไปแล้วกับงาน Mobile Conf 2018 ที่จัดโดยทีมงานคุณภาพตลอด 1 วันเต็ม และพลก็ได้มีโอกาสไปพบปะกับเพื่อนพี่น้อง และได้แชร์ความรู้ในฐานะ Speaker ของ Session ปิดท้าย

ไหนๆ ก็ไหนๆ แล้ว ขอมาสรุปเนื้อหาไว้ ณ ที่นี้สำหรับคนที่ไม่ได้มาร่วมงาน และให้ข้อมูลเพิ่มเติมด้วย

เพราะตอนเดโมเกิดขัดข้องทางเทคนิคนิดหน่อย มาใส่ล้ิงค์ไว้ให้ลองตรงนี้แล้วกันเนอะ

Session ของพลมี 3 ส่วนครับ.

Now. How. Adapt.

ในส่วนของเนื้อหา Session ของพล คือ “OK, Flutter” เป็นการสรุปสถานะของเครื่องมือ Cross Platform ในปัจจุบันว่าจะไปในทิศทางใด

ก่อนจะแนะนำตัว Google Flutter ว่ามีลักษณะเด่นอย่างไร และการเรียนรู้มันสามารถเริ่มต้นได้ยากง่ายแค่ไหน

1. Now: ทิศทางของเครื่องมือ Cross Platform ในปัจจุบัน

Now we have a lot of choice

หัวข้อนี้ เป็นความคิดต่อยอดจากเมื่อ 2 ปีที่แล้ว ที่พลได้ไปแชร์เรื่อง React Native ในงาน iOS Dev Meetup คลิกอ่านได้ใน link ครับ

จากปัจจุบันที่เครื่องมือ Cross Platform ทยอยกันดาหน้าออกมาเรียงแถวให้เราเลือกใช้งาน ซึ่งงานแต่ละแบบก็เหมาะกับ Framework หรือ Tools แต่ละตัว อย่างปัจจุบันก็มี

  1. Cordova (aka PhoneGap)
  2. Ionic
  3. Xamarin
  4. React Native
  5. NativeScript
  6. และอื่นๆ อีกมากมาย

และนอกจาก Google Flutter แล้ว เราก็น่าจะเห็นแล้วว่าน่าจะมีเครื่องมือ Cross Platform แนวเดียวกันนี้ออกมาอีกไม่น้อย

อย่าง Kotlin/Native ก็เป็นอีกตัวเลือกหนึ่งที่น่าสนใจ (ขอบคุณคุณบาสที่แนะนำ เราน่าจะได้เจาะกันเรื่องนี้ใน Meetup ครั้งหน้า แต่ขอไป “ย่าง” มันก่อน)

แต่ทำไมมันเยอะอย่างนี้ และที่สำคัญคือ Cross Platform กำลังเติบโตไปในทิศทางใด?

ความจริง: เราไม่ได้มีแค่ iOS และ Android

All platform mobile web and desktop

หากพูดถึง Platform หรือระบบที่เราพัฒนา Solution ไว้ใช้งานที่จับต้องกันได้ในชื่อของ “โปรแกรม” หรือ “แอพพลิเคชั่น” จริงๆ แล้ว จะพบว่าเราไม่ได้มีแค่ Mobile Platform อย่าง iOS และ Android

แต่จริงๆ เรายังมี Desktop Platform ที่ทำงานด้วยระบบปฏิบัติการ (OS) เก่าแก่อย่าง MacOS (ชื่อเดิมคือ OS X), Windows, และ Linux

ทั้ง 3 ตัวนี้ เคยเป็นโจทย์ที่พวกเราต้องเคาะให้ออกว่า จะพัฒนาโปรแกรมเดียวกัน แต่ใช้งานกันบน OS 3 ตัวนี้อย่างไร

ในยุคนั้น การปะทะที่ค่อนข้างชัดเจนที่สุดคงเป็น Java กับ .NET

และ Platform ที่ทรงพลังที่สุดในยุคของ Internet อย่าง Web Platform ที่ทำให้พวกเรามานั่งเขียน PHP, JSP, ASP.NET นี่ยังไม่นับของใหม่ๆ อย่าง Ruby on Rails หรือ Node.js นะ

และในยุค Web นี่แหละทำให้เราคิดว่า โลกนี้คงไม่มีอะไรมากไปกว่า Web Application อีกแล้ว (และสตีฟก็ทำให้เราคิดผิด)

นั่นทำให้จริงๆ แล้ว เรามีถึง 6 ระบบที่ลูกค้า หรือผู้ใช้สามารถใช้งาน Solution ของเราได้

  1. iOS
  2. Android
  3. MacOS
  4. Windows
  5. Linux
  6. Web

นี่มันมณีทั้ง 6 เม็ดบนถุงมือทานอสชัดๆ!!

และด้วยความหลากหลายของเครื่องมือ และภาษาโปรแกรมมิ่งที่หลากหลายในแต่ละ Platform ทำให้หลายๆ คนก็เริ่มคิดว่า ในเมื่อจริงๆ แล้ว มันก็คือระบบเดียวกัน ทำไมไม่เขียนทีเดียวจบ ใช้ได้หลายๆ Platform ไปเลย

Cross Platform เกิดขึ้นจากความขี้เกียจ

All cross platform framework and tools today

เครื่องมือ สร้างขึ้นเพื่อลดความลำบากของมนุษย์

และพูดได้ว่า Framework และ Tools ต่างๆ สร้างขึ้นมาเพื่อพิชิตข้อจำกัดเดิมๆ ของนักพัฒนา และตอบสนองความขี้เกียจนั่นเอง

จากโจทย์ที่ต้องตอบความต้องการถึง 6 ระบบด้วยกัน เอาแค่ iOS และ Android ก็มีเครื่องมือ ถึง 6 ตัวแล้ว (ไม่นับที่ล้มหายตายจากไปตามกาลเวลานะ)

ไม่ว่าจะเป็นแนวคิด Hybrid อย่าง PhoneGap, Cordova, Ionic

หรือจะพยายามเป็น Native อย่าง React Native, Xamarin, NativeScript, และ Google Flutter จะเข้ามาอยู่ในส่วนนี้

โอเค ถ้าเรามอง Google Flutter ตอนนี้ มันก็จะติดใจอยู่ว่า สร้างมาทำไม? ทำไมต้องใช้ Dart?

แต่ถ้าถอยออกมามองในภาพรวมที่กว้างขึ้น พลมองว่า การขยับของหลายๆ เจ้าในตอนนี้ กำลังพาเราเข้าสู่ยุคที่น่าสนใจกว่า ของการพัฒนาแอพพลิเคชั่น

เรากำลังเข้าสู่ยุค All Platform Framework

Capacitor and 6 OS target platform

จากที่เห็นว่า ถึงแม้เราจะมีเครื่องมือสร้างแอพพลิเคชั่นแบบ Cross Platform แต่จริงๆ แล้วมันยังจำกัดอยู่ที่ส่วนของ Mobile Application เท่านั้น ก็คือ Android และ iOS

แต่หากเราต้องการทำแอพพลิเคชั่นที่ตอบโจทย์แบบเดียวกัน ให้ใช้งานผ่านคอมพิวเตอร์ หรือเว็บได้ ก็อาจจะต้องมานั่งลุยกันยาวๆ ไม่ว่าจะเลือกภาษาโปรแกรม หรือ Framework ที่เหมาะสม

มีการขยับจากฝั่งของ Web Technology อย่าง Electron ที่เคยเล่าไปแล้วในงาน Meetup

แต่ Electron เป็นระบบที่แยกออกจาก Cross Platform Framework สำหรับ Mobile App อยู่ การทำเลยต้องทำมือด้วยตัวเอง

Cross Platform หลายๆ เจ้า ในตอนที่พลเล่นอยู่นี้ เริ่มเป็นรูปเป็นร่างให้จับต้องได้บ้างแล้ว เช่น Capacitor (เล่ารายละเอียดในนี้) ที่ออกตัวเป็น Runtime แบบ All Platform

ถึงแม้จะเป็น Product จากทีมงาน Ionic อันเลื่องชื่อ แต่ทีมงานก็บอกชัดเจนว่า ไม่ได้จำกัดว่าจะต้องใช้ Ionic Framework กับ Capacitor อย่างเดียว

ไม่ว่า React, Vue.js, Angular, หรือ JavaScript เพียวๆ ก็ใช้ Capacitor ทำ All Platform Application ได้

โค้ชพลเล่าวิธีการใช้งานไว้เบื้องต้นที่นี่

เดี๋ยวสิ้นเดือนนี้จะทำเป็นคลิปให้ดูใน Youtube channel นะ กดติดตามได้ 

Resource ที่เกี่ยวข้องกับหัวข้อนี้

2. How: Google Flutter ดีงาม, ยากง่ายแค่ไหน?

Google Flutter Compare with other framework

อย่างที่บอกว่า Google Flutter กำลังจะเป็นอีกตัวเลือกหนึ่ง สำหรับเครื่องมือสร้างแอพแบบ Cross Platform มีความยากง่ายในการเริ่มต้นยังไง

ภาพรวมเกี่ยวกับ Google Flutter

  1. เป็น Mobile Native UI Framework
  2. สร้างโดย Google
  3. ใช้ภาษา Dart เขียน
  4. รองรับ iOS และ Android (เป็นเครื่องมือ Cross Platform ประเภทหนึ่ง)
  5. ทำความเร็วได้ระบบ 60 เฟรมต่อวินาที (หนัง HD เนียนๆ เลย)

สำหรับการเริ่มต้นใช้งาน Flutter เพื่อให้ง่ายสำหรับคนที่เขียนโปรแกรมอยู่แล้ว คอนเซปต์ที่พลจะเอามาแชร์ให้ฟังเป็นดังนี้ครับ

UI และ Logic

ถ้าไม่นับโครงสร้าง Architecture อย่างพวก MVC, MVVM, หรือ VIPER (อันนี้ของใหม่ เพิ่งรู้จากคุณบาสในงาน) ส่วนของแอพพลิเคชั่น จะแบ่งของเป็น 2 ส่วนใหญ่ๆ นั่นคือ

  1. User Interface
  2. Logic

ซึ่งใน 2 ส่วนนี้มี 2 แนวคิดใหญ่ๆ ที่ใครเป็นอยู่แล้ว ก็จะเข้าใจการใช้งาน Google Flutter ได้ในเวลาอันรวดเร็วเลยครับ

1. Dart เป็​น OOP

OOP หรือ Object-Oriented Programming เป็นแนวคิดที่กำหนดโครงสร้างภาษาโปรแกรมมิ่งหลายตัวมากๆ

อย่างเช่นใครเขียน Java, .NET, หรือ TypeScript มา จะเข้าใจ Dart ได้ไม่ยากเลย เพราะแรงบันดาลใจมาเต็ม เช่น

  • Class
  • Inherit ด้วย extend
  • Property กับ Method
  • DataType ก็มี
class NextflowDev extends NextflowEmployee {
   
    String name;

    void code() {
       print('Coding...');
    }

}

2. User Interface เป็นแนว Reactive Programming

Bento as concept of Flutter UI

User Interface ที่เราเห็นบน Flutter เรียกว่า Widget

และ Widget สามารถประกอบเข้าด้วยกันได้ เหมือนจัดวางอาหารลงไปในช่องข้าวกล่อง

ในโลกของการสร้าง User Interface ยุค Web แบบดั้งเดิม เห็นจะไม่มีอะไรมาแทนที่การเขียนแนว XML ที่แสนฮิตไปได้ เช่น

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Mobile Conf 2018</title>
</head>
<body>
    Thailand Mobile Conference 2018
    <img src="assets/mobile_conf2018.png" width="100%" alt="">
</body>
</html>

หรือ XAML ใน Xamarin.Forms

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.MainPage">

    <StackLayout>
        <!-- Place new controls here -->
        <Label Text="Welcome to Xamarin Forms!" 
               VerticalOptions="Center" 
               HorizontalOptions="Center" />
    </StackLayout>

</ContentPage>

เพียงแต่ถ้าเรามาใช้งาน Flutter ตอนเขียน UI จะรู้สึกแปลกตามากๆ โดย Google เตรียม User Interface เป็นชิ้นๆ ไว้ให้เราแล้ว แต่เราก็สามารถเอามาประกอบใช้งานเองได้

ซึ่งใน Flutter เราจะเรียก UI แต่ละส่วนว่า Widget (คำนี้จำไว้เลย จะเห็นอยู่ทุกที่ใน Flutter)

เช่นการสร้างแอพ 1 หน้า แบบมีข้อความน่ารักๆ ไว้ตรงกลางจะเป็นโค้ดแบบนี้

void main() {
  runApp(MaterialApp(
      title: 'Nextflow What to eat',
      debugShowCheckedModeBanner: false,
      theme:  ThemeData(primarySwatch: Colors.blue, fontFamily: "Noto"),
      home:  Scaffold(
        appBar:  AppBar(
          title:  Text("What to eat"),
        ),
        body:  Center(
          child:  Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
               Text(
                'สวัสดีจ้ะ',
                style: TextStyle(fontSize: 60.0),
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: Text('จัดมา')
        ));,
    ););
}

สำหรับคนที่ไม่เคยเขียน React มา ไม่น้อยเลยที่จะอุทานว่า “อุ๊ต๊ะ”​ (หรือคำประจำตัวของแต่ละคน) เพราะการเขียนแบบนี้มันเหมือนกับ…

การ New object ซ้อนๆ กัน หรือ เรียกว่าเป็น Constructor ซ้อน Constructor ก็ได้

แต่ถ้าเราเข้าใจว่า:

สิ่งที่เห็นนี้ คือการสร้าง Widget ของ UI แต่ละชิ้นซ้อนๆ กัน เหมือนกับการจัดวางอาหารลงไปในข้าวกล่อง มันจะดูง่ายขึ้นเยอะ

เช่นถ้าโค้ชพล ใช้ สีแยก Widget แต่ละอันออกจาก Widget ตัวใหญ่ที่ชื่อว่า Scaffold

Break down Scaffold Widget in Google Flutter

เราจะเห็นว่าจริงๆ แล้ว มี Widget 3 ตัวที่ถูกกำหนดให้ลงไปอยู่ในแต่ละส่วนของ Widget ที่ชื่อ Scaffold ของเรา นั่นคือ

  1. AppBar
  2. Center (ตัวนี้เป็น Widget ที่ใช้จัด Layout ตัวหนึ่ง)
  3. Floating Action Button

เราก็จะมองเห็นว่า จริงๆ แล้วมันเป็นโครงสร้างง่ายๆ แนวเดียวกับ HTML ในการเขียนเว็บ หรือใน React Native นั่นเอง

ด้านล่างนี้เปรียบเทียบระหว่าง การเขียน Web UI ด้วย HTML กับ CSS และการเขียน Mobile App UI ด้วย Dart ใน Flutter

Web UI (HTML/CSS)

<div class="greybox">
    Lorem ipsum
</div>

.greybox {
      background-color: #e0e0e0; /* grey 300 */
      width: 320px;
      height: 240px;
      font: 900 24px Georgia;
    }

Mobile App UI (Dart)

Container( // grey box
  child: Text(
    "Lorem ipsum",
    style: TextStyle(
      fontSize: 24.0
      fontWeight: FontWeight.w900,
      fontFamily: "Georgia",
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

Reactive UI ใน Flutter

Stateful and Stateless Widget in Google Flutter

ทีนี้พอเราสร้าง User Interface ด้วยภาษา Dart ไปซักพัก เราก็จะเริ่มรู้ว่า นี่มันเหมือนกับการสร้างหน้าเว็บนิ่งๆ อันหนึ่งนี่นา

แต่แอพพลิเคชั่นมันต้องมีส่วนของ Logic มีการอัพเดต รับข้อมูลเอาไปใช้งานใช่ไหม ส่วนนี้แหละที่ OOP ของ Dart จะเข้ามามีบทบาทสำคัญ

นั่นคือ Flutter มีการกำหนด Class ที่ชื่อ StatefulWidget  และ StatelessWidget  ขึ้นมาให้เราเลือกสร้าง Widget ของเราอีกที

แนวคิดการเลือกใช้ Class ทั้ง 2 ตัวมีดังนี้

  • Stateful Widget จะใช้กับส่วนของแอพที่มีการอัพเดต เปลี่ยนแปลงข้อมูล
  • Stateless Widget จะใช้กำหนดส่วนของแอพที่แสดงขึ้นมาเฉยๆ ไม่มีการเปลี่ยนแปลงอะไร

เช่น Logo ของบริษัทเราในแอพ อาจจะมองเป็น Stateless Widget ได้ (ลองสังเกตว่า Class นี้ return เป็น Widget ที่ชื่อ Container ที่มีรูปโลโก้เว็บไซต์พลไปใช้งาน)

class Logo extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return  Container(
       child: Image.asset('Nextflow.png')
    );
  }

}

แต่เราพูดถึง List ที่แสดงรายชื่อหุ้น ที่อัพเดตอยู่เป็นระยะ เราก็อาจจะสร้างเป็น Stateful Widget ได้

class StockList extends StatefulWidget {

@override
  _StockListState createState() => new _StockListState();

}

class _StockListState extends State<StockList> {
   
    // เรียกข้อมูลจาก Web API
    void updateData() {
       setState(() {
           // update stock data
       });
    }

    @override
    Widget build(BuildContext context) {

       return ListView.builder(...)

    }

}

ตรงจุดนี้ถ้าเริ่มเขียนแอพพลิเคชั่นขึ้นมาด้วย Google Flutter สัก 2-3 ตัว ก็จะเริ่มจับทางได้ว่า จะสร้าง Stateful หรือ Stateless Widget ดี

Resource ที่เกี่ยวข้องกับหัวข้อนี้

3. Adapt: Cross Platform ที่ดีต้องเปิดช่องทำงานกับ Native API ได้ง่าย

Cameleon

จากการใช้งานเครื่องมือ Cross Platform มาตั้งแต่ยุคเริ่มต้น จุดหนึ่งที่เครื่องมือ Cross Platform กำลังเดินหน้าไป คือการเชื่อมต่อส่วนของ Cross Platform เข้ากับ Native API ได้ง่ายขึ้น

เพราะในอดีต นักพัฒนาจะคุ้นเคยกับการที่เจ้าของ Framework เตรียม Plugin ที่จะติดต่อกับ Native API (อย่างพวกตัวแสกน Barcode หรือ Apple Pay)

แน่นอนว่าสะดวกดี แต่ในงานพวกเราลงความเห็นตรงกันว่า การโยนความหวังไว้ที่นักพัฒนา Plugin บางตัว ทำให้เกิดความเสี่ยงต่อชีวิต และการลงทุนของพวกเรา เพราะบางครั้งกว่า Plugin ตัวที่ใช้จะอัพเดตตามเวอร์ชั่นใหม่ของ OS ก็อาจจะมี bug ที่ทำให้แอพพลิเคชั่นเราใช้งานไม่ได้

และตอนนี้พลมองเห็นว่า เจ้าของ Framework แต่ละเจ้า รับทราบจุดด้อยตรงส่วนนี้แล้ว และปรับปรุงให้เราสามารถเขียนโค้ดเรียกใช้ Native API ได้โดยตรง (และต้องง่ายด้วยนะ) เพื่อลดความเสี่ยง

และทำให้นักพัฒนามีอำนาจในการควบคุมการติดต่อกับ API ของระบบโดยตรง

ต่อไป Cross Platform Framework จะให้เราเรียกใช้ Native API โดยตรง

ในส่วนของ Cordova และ Ionic ก็รู้ตรงจุดนี้ดี ดังนั้นใน Capacitor ที่ถูกออกแบบมาเป็น Runtime กลางตัวใหม่ จึงมี “ส่วนเชื่อมต่อ” ท่ีว่านี้เรียบง่ายขึ้นมาก (ดูตัวอย่างสำหรับ Android และ iOS)

สำหรับ Google Flutter มีส่วนของ Dart Package มาให้ก็จริง แต่จุดแข็งของมันคือ “ส่วนเชื่อมต่อ” ระหว่าง ภาษา Dart กับภาษา Programming ในส่วน Native ที่ทำการบ้านมาค่อนข้างดี

อย่างการติดต่อกับโค้ด Swift กับ Kotlin (โปรเจคที่เขียนด้วย Java กับ Objective-C ก็ใช้ได้นะครับ) ในแอพ iOS และ Android ที่ดูแล้วก็เป็นการพัฒนาเทคนิคจาก React Native และ Xamarin ให้เรียบง่ายมากขึ้น ที่สำคัญ Document ละเอียดและชัดเจน

สรุปเนื้อหา Session จากงาน Mobile Conf 2018

เอาล่ะ ค่อนข้างยาว แต่รายละเอียดที่พูดในงานก็น่าจะครบถ้วน จะมี Demo นี่แหละที่ติดขัดนิดหน่อย แต่ก็เอามาทำเป็นคลิป YouTube ให้แล้วเนอะ

ดังนั้นเนื้อหาโดยรวมก็คือ

  1. Now: Cross Platform ตัวใหญ่ๆ แต่ละเจ้า กำลังเพิ่มศักยภาพของตัวเอง ให้รองรับ Platform มากขึ้น เช่น Desktop และ Progressive Web App
  2. How: Google Flutter เป็นน้องใหม่ ที่เรียนรู้ค่อนข้างง่าย ใช้แนวคิด OOP กับ Reactive ที่ปัจจุบันก็เป็นที่กว้างขวาง
  3. Adapt: Cross Platform ในตอนนี้ และอนาคต จะลดความเจ็บปวดของนักพัฒนาโดยเปิดให้เรียกใช้ Native API ได้โดยตรง ไม่ต้องรอ 3rd-party Plugin

โลกของ Cross Platform นี่น่าตื่นเต้นจริงๆ

กดติดตามอัพเดตกับโค้ชพลได้ทาง:

ถ้าต้องการให้ไปช่วยอบรม หรือแนะนำทีมในองค์กร ก็ดูหลักสูตรปัจจุบันที่พลทำให้ได้ที่ส่วนของ การเรียนและอบรม Training หรือโทร 083-071-3373 ได้นะ

เปิดอบรมสร้าง Cross Platform Mobile Application ด้วย Ionic Framework

เหมาะสำหรับคนทำเว็บ, เริ่มต้น JavaScript ES6 และ Angular เข้าใจง่าย, ใช้ได้จริง

สอบถาม หรือติดต่อจัดอบรมโทร 083-071-3373

โปรหน้าฝน! เรียนรอบสด รับคอร์สออนไลน์มูลค่ากว่า 5800 บาทฟรี!

เริ่มต้นยุค AI ด้วยคอร์สฟรี และพรีเมี่ยม กับพล

หากชอบสิ่งที่พลเล่า เรื่องที่พลสอน สามารถสนับสนุนพลโดยการเข้าเรียนคอร์สออนไลน์ของพลนะคร้าบ

  • เข้าใจง่าย ใช้ได้จริง ออกแบบการสอนอย่างเข้าใจโดยโค้ชพล
  • มีคอร์สสำหรับคนใช้งานทั่วไป จนถึงเรียนรู้เพื่อใช้งานในสายอาชีพขั้นสูง
  • ทุกคอร์สมีใบประกาศณียบัตรรับรองหลังเรียนจบ

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

Privacy Preferences

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

Allow All
Manage Consent Preferences
  • Always Active

Save