Manage build scheme in Xcode
iOS env using custom build scheme
หลายปีที่ผ่านมาคงจะปฏิเสธไม่ได้ว่า การสร้าง Mobile Application นั้นมีความหลากหลายมากขึ้น ไม่ยึดติดในเรื่องของตัวภาษา (Native Language) อีกต่อไป และอีกทั้งยังเป็นยุคที่มีบทบาทสำคัญในการสร้างสิ่งที่เรียกว่า Cross Platform อีกด้วย เรียกได้ว่าเทคโนโลยีได้เปลี่ยนแปลงหลายๆอย่าง ทำให้เราทำงานได้สะดวกขึ้น เร็วขึ้น ง่ายขึ้น แต่ยังแฝงไปด้วยความเจ็บปวดในการพัฒนาแอพที่ดีมากขึ้นอีกด้วย
สิ่งที่เกริ่นนำมาทั้งหมด ไม่ใช่ Xamarin หรือ Ionic framework ฯลฯ แต่ยังคงเป็น React Native นั่นเอง และนั่นคือสิ่งที่เราจะมาพูดถึงกันในบทความนี้
ในบทความนี้ผมจะมาเล่าในสิ่งที่มีความสำคัญในการสร้าง Cross Platform Application อย่างมาก นั่นก็คือเทคนิคในการเปลี่ยน Environment ในการพัฒนา เช่น ตอนนี้เรากำลัง Build มันอยู่ที่ Staging หรือ Development หรือ Production เป็นต้น ซึ่งมันควรจะทำได้สะดวก แล้วก็ง่ายด้วยครับ จะมีประโยชน์มากหากเราใช้ร่วมกับ Automation build tools ต่างๆ และที่สำคัญมันต้องทำงานได้ทั้งสองแพลตฟอร์ม
ก่อนที่จะเริ่ม เราลองมาคิดกันเล่นๆก่อนว่าแอพของเรานั้น ต้องการให้ build ใน environment ใดบ้าง เช่น development, staging, production เพราะสิ่งเหล่านี้เราจะส่งมันไปบอก JS เพื่อให้จัดการต่อ
iOS / Xcode Project Setting
เอาล่ะ… เรามาเริ่มที่ฝั่ง iOS กันก่อน ซึ่งแน่นอนมันจะต้องเริ่มด้วยการ Setting ค่าบางอย่างบน Xcode Project ก่อนที่จะ Bridge และส่งไปให้ฝั่ง JS ได้ใช้
Use Different Build Config
ถ้าเราใช้ react-native cli ในการ generate app เราจะมี Configuration สองตัวก็คือ Debug / Release เป็นมาตราฐาน แต่ในครั้งนี้เราต้องการ Staging environment ด้วย ให้เราเพิ่มเข้าไปก่อนโดยต้อง Duplicate Release มาใช้งานแบบนี้
จากนั้นให้เราเปลี่ยนชื่อมันเป็น Staging ได้เลย …ที่ต้อง Duplicate Release มาก็เพราะว่าเวลา build react native นั้น ตัวมันเองจะ dependent กับ config บางอย่างใน build นั้นๆ ซึ่งครั้งนี้เราต้องการ config ของ Release นั่นเอง
Prepare to Native Module
ตอนนี้เราสร้าง Build Configuration เรียบร้อยแล้ว เพื่อใช้สำหรับกำหนด build config ในการ compile ดังนั้นเราจะใช้ User-Defined setting ของ Xcode ในการเก็บ environment ซึ่งผมจะตั้งชื่อ key ของมันว่า BUILD_ENV เพื่อใช้ในการเก็บ value ของ env ต่างๆ ก็คือ development, staging, production ส่วนวิธีสร้างให้เรามองหา Build Setting ของ Project แบบนี้
ถ้าใครยังไม่ทราบ Xcode นั้นจะแบ่งการเก็บค่าของ Setting ต่างๆไว้สองส่วนคือ ส่วนที่เป็นระดับ Project (มองง่ายๆคือ Global) และส่วนที่เป็น Targets (ในระดับ scheme ต่างๆ เพราะเราสามารถ build apps ได้หลาย scheme หลาย configใน Project เดียว)
ถ้าเปรียบกับฝั่ง Android มันก็จะคล้ายกับไฟล์ build.gradle ของ Project และ build.gradle ของ App นั่นเองครับ
จากนั้นให้กดเครื่องหมาย “+” เพื่อสร้าง User Define Setting ขึ้นมาใหม่ แล้วให้ value ของแต่ละ config เป็นชื่อของ env ที่เราต้องการ (development, staging, production)
Create variable in plist
User-Defined settings ที่เราสร้างไว้ข้างต้นนั้น มันจะไม่สามารถ access จาก code ได้โดยตรง (ไม่สามารถเขียน code เพื่อดึง key/val ออกมา) เพราะหน้าที่ของมันเป็นเพียงแค่เก็บ config ที่ใช้ในการ build เท่านั้น
ดังนั้นเราจึงต้องสร้างตัวแปรเก็บไว้ในไฟล์ Info.plist
ซึ่งผมจะใช้ชื่อ key เป็น BuildEnvironment
และ value เป็น $(BUILD_ENV)
การสร้างตัวแปรแบบนี้ใน plist file ซึ่งมันจะถูกนำไป map เข้ากับ config ของเราโดยอัตโนมัติ นั่นหมายความว่าเราจะสามารถเขียน code ให้ดึง variable ออกมาได้ง่าย
Create an Native Module
ตอนนี้เรามี key ใน plist ที่ชื่อว่า BuildEnvironment
แล้ว ถึงเวลาที่เราต้องสร้าง Native Module ขึ้นมาเพื่อดึง value จาก plist file
แล้ว bridge กลับส่งไปให้ JS รับรู้และเรียกใช้ต่อไป
ข้อดีอีกอย่างของ React Native คือเราสามารถสร้าง Native Module ต่างๆเพื่อนำมาใช้งานต่อในฝั่ง React JS ได้ เช่น API บางอย่างที่ยังไม่มีบน iOS หรือการ Custom งานบางอย่างที่ต้องใช้ Objective-C ช่วยทำงาน (เป็นอีกเหตุผลที่คนเขียน React-Native ควรมีพื้นฐานภาษา และเข้าใจ anatomy ต่างๆเหล่านี้มาพอสมควร)
ก่อนอื่นต้องสร้าง Class ใหม่ขึ้นมา ผมแนะนำว่าให้เป็นชื่อกลางๆ เช่น RNConfig.h
และกำหนด subclass
เป็น NSObject
ในการสร้าง Native Module นั้น facebook ได้แนะนำไว้ให้เราแล้ว ตามนี้ https://facebook.github.io/react-native/docs/native-modules-ios.html ซึ่งจะมีตัวอย่างการ implement แบบนี้
จาก class ข้างต้นจะอธิบายได้ว่าเราอ่านข้อมูลจาก key ที่อยู่ใน info dictionary ของเราไว้ใน NSString แล้ว return มันออกไปด้วยเมธอต constantsToExport
ที่ React จัดเตรียมไว้ให้
Noted:
constantsToExport
นั้นจะใช้ export ของที่เป็น static ซึ่งมันจะทำงานตอน initialize เท่านั้น หากมีการเปลี่ยนแปลงค่าในขณะ runtime บน JS จะไม่ทำงาน
Implement config in JS
ตอนนี้เราได้ Native Module ที่เอาไว้ใช้เลือก environment แล้ว และสามารถ Import มาใช้ได้แล้ว แต่เราควรสร้าง config file ที่เป็น JSON ไว้ด้วยเพื่อให้มันยืดหยุ่นในการทำงานและแก้ไขในอนาคตได้มากที่สุด และ ในอนาคตเราอาจต้องเพิ่ม key ที่เจาะจง เช่น GA Tracking code หรือ feature toggle ต่างๆ ซึ่งอาจจะทำในลักษณะนี้
จากนั้นเราแค่สร้าง JS file เพื่อที่จะ return key หรือ environment ต่างๆไว้ใช้งานแบบนี้
แค่นี้เราก็สามารถกำหนด Build configuration ได้อย่างยืดหยุ่น และไม่อิงฝั่ง Native จนมากเกินไป เช่น ต้องการเปลี่ยนเป็น Production ก็สามารถทำได้และ archive แอพได้ทันที
Where To Go From Here?
ตอนนี้เราสามารถ เปลี่ยน build environment ได้อย่างง่ายๆแล้ว แค่กำหนด build config ก่อนการ build ในแต่ละครั้ง (สามารถทำบน cli ได้ ในกรณีทำ CI ด้วยคำสั่ง xcodebuild -workspace "ios/MYApp.xcworkspace" -configuration "Staging" "clean" "archive"
) ในบทความนี้ถ้าจะนำไปใช้จริง ควรใช้วิธีเพิ่ม build target scheme ขึ้นมาอีกตัว จะได้สามารถแยก Build version ได้อย่างเจาะจงมากขึ้นครับ ส่วนในบทความต่อไปจะพูดถึงวิธีในการ setting ของฝั่ง Android กันบ้าง ว่ามีอะไรบ้าง… รอติดตามครับ :D