Series -Flutter CI/CD, ตอน: วิธีสร้าง env ต่างๆใน Flutter (Version iOS)

Amorn Apichattanakul
5 min readJan 11, 2021

--

ในการพัฒนา Mobile application ไม่ว่าจะ native หรือ cross platform ยังไงเราก็ต้องมี env ต่างๆ เพื่อใช้ในการพัฒนา

คงอาจจะมีบางคนที่ยังไม่เคยแยก env ต่างๆ เราก็มาเรียนรู้การแยกกันได้เลยครับดังนี้

ทำไมเราต้องแยก?

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

Develop env — สำหรับ dev อย่างเดียว เราสามารถยำอะไรกันในนี้ได้ เวลาพังจะได้ไม่กระทบต่อ tester หรือคนอื่นๆ โดยปกติ ก็จะเป็น branch develop เนี่ยละครับ

Staging env — สำหรับ dev กองกลางครับ คือบางที code เราอาจจะต้องแชร์กับทีมอื่น เราก็ต้องเทสส่วน develop ให้เรียบร้อย แล้วอยากเริ่ม integrate เข้ากับทีมอื่นเราก็จะมากันที่ env นี้

SIT env — จะเป็น env สำหรับ dev ที่ต้องการจะส่ง test ซึ่งในหลักของ Agile เราก็ test early ครับ จะได้แก้ได้ก่อน ที่จะยุ่งยากเกินไป อันนี้ก็จะเป็นของ tester ละครับ คือ code เรานิ่งแล้วเทสแล้ว พร้อมจะลอง test แล้ว จะส่งมาส่วนนี้ โดยมีการ implement ของหลายๆทีมเข้ามาด้วยกันจนเป็น feature ที่สามารถเทสได้

UAT env — จะเป็น env สำหรับ PO (Project Owner) หรือ พวก internal end user แล้วละครับ คือหลังจากผ่าน SIT env มา tester จะ approve ให้เรียบร้อยแล้วว่าเทสได้ตามที่ต้องการ สามารถเข้ากับระบบใช้งานได้ ก็จะมาให้ User Acceptant test ละครับ ว่ามีอะไรอยากปรับแก้ไหม ทำงานถูกต้องไหม โดยส่วนใหญ่เคสนี้จะทำงานได้ค่อนข้างสมบรูณ์แล้วครับ แค่จะปรับ look and feel เท่านั้นที่เหลือ

Production env — ก็ตามชื่อครับ คือ end user เลยครับ เราไม่เอา real man test in Production นะครับ นอกจากว่ายังดูไม่ดีในสายตาคนใช้แล้ว ยังทำให้คนใช้งานเห็นว่าไม่มีความเป็น professional อีกด้วย

แต่ถ้าเอาแบบง่ายๆ ทีมเล็กๆ ไม่ต้องคิดมากก็จะเป็น non-prod กับ prod env ครับ 2 env พอ

โอเคครับ ต่อไปเราจะแยก file main.dart ของ Flutter ออกมา เนื่องจากว่าปกติ Flutter จะให้เรา run ที่ main.dart ครับ ให้เรา copy code จาก main.dart แบ่งออกมาเป็นแต่ละ env เลยครับ

เราก็จะแบ่งออกเป็น

main_dev.dartmain_stg.dartmain_sit.dartmain_uat.dartmain_prod.dart

ซึ่งด้านในภาพรวมส่วนใหญ่ code จะเหมือนๆกัน แต่ initialize ข้อมูลต่างกัน โดยปกติจะเป็นพวก path API, path asset, และข้อมูลต่างๆที่จะ point ไปคนละที่กันครับ

เวลาเรารันต่อไปเราจะ run command

flutter run -t lib/main_dev.dart

แทนที่จะรัน command ของ main.dart ปกติ ทีนี้ API เราก็จะ point ไปคนละที่ละครับ

แต่แค่นี้ยังไม่พอครับ เราจะใช้อีกส่วนหนึงที่ Flutter มี ที่เรียกว่า Flavor

iOS จะใช้ชื่อว่า scheme

Android ชื่อว่า BuildFlavor

ซึ่งตัว flavor ของ Flutter นี้จะทำให้แบ่ง asset ของ แต่ละ env ได้อีกครับ ซึ่ง Flavor นี้จะต้องใช้กับ native ครับ เราต้องไป implement เพิ่ม โดยปกติก็จะใช้กับการแบ่ง Firebase หรือ Native SDK อื่นๆ ต่อไปเราก็จะมี command ใหม่คือ

flutter run --flavor dev -t lib/main_dev.dart

ซึ่ง flavor ในที่นี่ก็จะชื่อ dev ครับ ซึ่ง flavor dev นี้เราต้องไป setup ที่ Xcode, Android Studio เพิ่มครับ มาดูกัน

Xcode

ไปที่ folder iOS นะครับ แล้ว double click ที่ Runner.xcworkspace นะครับ อย่าไปใช้ Runner.xcodeproj เด็ดขาดนะครับ ต่างกันยังไง?

xcworkspace จะะเป็น project ที่รวมกับ cocoapods เรียบร้อยแล้ว (dependency manager ของ iOS) แล้วเราจะทำงานตรงส่วนนี้ทั้งหมดครับ แล้วก็อย่าไปลบ Runner.xcodeproj นะครับ เราใช้นะครับ แต่แค่จะไม่ได้เอามาใช้การ coding

จะได้ตามหน้าต่างด้านล่างครับ แล้วคลิกที่ส่วนสีแดง

แล้วก็ไปที่ Manage Schemes…

จะมีหน้าต่าง popup ขึ้นมา ให้ click ที่ Runner scheme แล้วไปเลือกที่เฟืองด้านซ้ายล่าง แล้วกด Duplicate ครับ

แล้วต้องชื่อใหม่ว่า dev ชื่อนี้สำคัญนะครับ ต้องเป็นชื่อเดียวกันกับ flavor ใน Flutter

เสร็จแล้วก็ไปทำอีกอันครับ ชื่อ production ชื่อก็จะเหมือนกันครับ ชื่อ flavor กับชื่อ scheme จะต้องตรงกัน พอทำทั้งคู่เสร็จเรียบร้อยให้ไป tick Shared ที่ด้านหลังของ scheme ด้วยนะครับ

ต่อไปนะครับ เราจะไปที่ Runner แล้วไปเพิ่ม Configurations ที่เครื่องหมายบวกด้านล่าง

กด + แล้วมันจะถามว่า duplicate from Debug, Release หรือ Profile

ให้กด duplicate Debug 1 ครั้ง Release 1 ครั้ง แล้ว rename ตามที่ชอบได้เลยครับ ปกติผมจะทำออกมาเป็น 4 แบบ คือ

  1. Debug-non-prod คือ Build ลง device ที่ชี้ไปที่ non-prod
  2. Debug-prod คือ Build ลง device ที่ชี้ไป prod
  3. Release-non-prod คือ สำหรับ submit TestFlight ชี้ไปที่ non-prod
  4. Release-prod คือ สำหรับ submit TestFlight ชี้ไปที่ prod

จำนวน Configuration ก็คือ ENV x 2 ครับ ถ้าจะมี 3 ENV ก็จะมี 6 Configurations

หลังจากสร้าง Configurations แล้วให้ไป Edit Scheme แล้วไปที่ Archive

เพื่อแก้ Build Configurations ให้เป็นตามที่เราสร้างมาใหม่ ภาพด้านล่างจะเห็นว่า scheme Production ผมจะให้ point ไปที่ Release-prod ครับ ทำแบบนี้กับทุก env โดยเลือก Build Configuration ให้ตรงกับ scheme และแก้แค่เฉพาะ Archieve พอนะครับ เพราะพวก Test, Run, Analyze เราทำใน Flutter

หลังจากนั้นไปที่ Signing & Capabilities นะครับ จะเห็นว่า Capability เราเพิ่มขึ้นมาตามจำนวน Configurations ซึ่งเราจะใช้ Fastlane จากบทความที่แล้วมาสร้าง Certificate ให้กับ แต่ละ Configurations กัน

เมื่อทำ ceritificate แล้วก็ให้จัดการ assign team กับ certificate ให้ถูกต้อง ถ้าถูกต้องเรียบร้อยจะต้องไม่มีสีแดงขึ้นมา

ต่อไปเราจะมาทำการแบ่ง asset แยกสำหรับ non-prod กับ prod นะครับ ผมมายกตัวอย่างกับ Firebase นะครับ

Firebase การที่เราจะติดตั้งได้เราจะต้องไป setup Project Firebase ก่อน ทั้ง 2 ส่วน non-prod และ prod

หลังจากเสร็จแล้ว Firebase จะให้เรา download GoogleService-Info.plist แล้วให้เราลากเข้า Project ครับ หลังจากลากเรียบร้อยแล้ว เนื่องจากเราจะมี GoogleService-Info.plist อยู่ 2 ตัว คือ non-prod กับ prod ผมจึงใช้ post script เพื่อที่จะมา copy file มาลง Xcode ขึ้นอยู่กับ Scheme ที่เราเลือก โดยทำดังนี้

ไปที่ Build Phases แล้วเลือกเครื่องหมายบวก เพื่อใส่ New Run Script Phase

ตั้งชื่อตามต้องการได้ครับ ผมตั้งชื่อว่า Copy Asset ตรงนี้สำคัญมาก เรื่องลำดับ

เราจะต้องลากมาไว้ก่อน Compile Sources, Link Binary, Copy Bundle นะครับ ไม่งั้น Asset จะถูก Build เข้าไปก่อนที่เราจะเปลี่ยน

ใส่ Command นี้เข้าไปที่ script

# Type a script or drag a script file from your workspace to insert its path.echo "Current Config ======= ${CONFIGURATION}"if [ "${CONFIGURATION}" == "Debug-non-prod" ] || [ "${CONFIGURATION}" == "Release-non-prod" ]; thencp -r "${PROJECT_DIR}/dev/GoogleService-Info.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"echo "Development plist copied"elif [ "${CONFIGURATION}" == "Debug-prod" ] || [ "${CONFIGURATION}" == "Release-prod" ]; thencp -r "${PROJECT_DIR}/production/GoogleService-Info.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"echo "Production plist copied"fi

หลักๆก็คือเราจะ copy GoogleService-Info.plist ของแต่ละ ENV ลงมาที่ Project ครับ ซึ่ง Path GoogleService-Info.plist ให้ดูดีๆนะครับ บางคนอาจจะไม่เหมือนกัน ผมวางไว้ง่ายๆไว้ที่ Root ของ Runner project เลย

ส่วนที่ root Runner ผมจะสร้าง folder ชื่อ dev กับ production เอาไว้ แล้วเอา GoogleService-Info ที่ถูก env ไปวาง พอตอนที่เรา Build script นี้จะถูกรันครับ แล้ว asset ของแต่ละ env จะถูก copy ลงมาไว้ให้ถูกต้องครับผม

ซึ่งถ้ามี Asset อื่นๆ เช่น Info.plist หรืออะไรก็ตามที่ทาง SDK หรือ lib อื่นๆ ใช้ก็นำ script นี้มาประยุกต์ได้ครับ เราก็เพิ่ม cp -r เข้าไปอีก เพื่อให้มัน copy เพิ่มได้ครับ เช่น

cp -r "${PROJECT_DIR}/production/GoogleService-Info.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
cp -r "${PROJECT_DIR}/production/Info.plist" "${PROJECT_DIR}/Runner/Info.plist"
cp -r "${PROJECT_DIR}/production/mySDK.plist" "${PROJECT_DIR}/Runner/mySDK.plist"

เป็นต้นครับ

ก็จะเหลือขั้นตอนสุดท้ายแล้วครับ คือการทำ User-Define เพื่อให้แต่ละ Build Configuration เราสามารถมีชื่อ app หรือว่า bundle ID ที่ต่างกันได้

ไปที่ Target Runner นะครับ แล้วเลือก Build Settings

เลื่อนไปล่างสุดเลยครับ จะมี section User-Defined ครับ

กด + ครับ แล้วเลือก Add User-Defined Setting

เสร็จแล้วก็ใส่ชื่อได้เลยครับ ผมเลือกชื่อ key คือ DISPLAY_NAME

แล้วก็ยืด options ออกแล้วก็ใส่ชื่อตาม Configuration ที่เราเลือกเลยครับ

พอใส่เสร็จแล้วเราให้ไปแก้อีกที่ครับ อันนี้จะแก้ Product Bundle Identifier

ก็ให้แก้แต่ละ Configuration ให้ถูกกับ Bundle ID ของเราเลยครับ

ขั้นตอนสุดท้ายเอาคำว่า

$DISPLAY_NAME 

ไปใส่ที่ Display name ได้เลยครับ มันจะเอาค่าของเราไป replace ขึ้นอยู่กับ Configuration ให้

ก็จะเป็นอันเรียบร้อยครับ สำหรับการติดตั้ง Scheme, Configuration และ Script สำหรับ copy asset ต่างๆ ที่นี้เราใช้ command

flutter run flavor --dev -t lib/main_dev.dart 

ได้เลยครับ ก็จะชี้ API ไปที่ dev โดยใช้ asset, Appname, bundle ID, certificate ต่างๆ สำหรับ app dev ครับ

บทความต่อไปจะมาสอนวิธีเช็ต env ของ Android นะครับ เพื่อที่จะใช้กับ Flutter และเข้ากับ scheme ของ iOS

--

--

Amorn Apichattanakul
Amorn Apichattanakul

Written by Amorn Apichattanakul

Google Developer Expert for Flutter & Dart | Senior Flutter/iOS Software Engineer @ KBTG Join us for more info https://www.facebook.com/groups/1565880160729817

No responses yet