Flutter — สร้าง Boilerplate Project ที่สมบูรณ์แบบตั้งแต่เริ่มต้น

Dec 01 2022
เครดิต: Nguyễn Thành Minh (นักพัฒนา Android) เราทุกคนเคยผ่านมาแล้ว — ที่ผมหมายถึงคือเราทุกคนพยายามสร้างโครงการสำเร็จรูปสำหรับการทำงานหรือเป็นโครงการส่วนตัว โครงการสำเร็จรูปที่ดีจะช่วยเราประหยัดเวลาได้มากด้วยการแก้ปัญหาทั่วไป เช่น การเรียก API การประมวลผลข้อมูลจากฐานข้อมูล และบางทีที่สำคัญที่สุด — ทำให้โค้ดของเราเป็นระเบียบและสอดคล้องกัน เพื่อให้ผู้คนมีเวลาพยายามทำความเข้าใจโค้ดของเราได้ง่ายขึ้น

เครดิต: Nguyễn Thành Minh (นักพัฒนา Android)

เราทุกคนเคยผ่านมาแล้ว — ที่ฉันหมายถึงคือเราทุกคนพยายามสร้างโครงการสำเร็จรูปสำหรับการทำงานหรือเป็นโครงการส่วนตัว โครงการสำเร็จรูปที่ดีจะช่วยเราประหยัดเวลาได้มากด้วยการแก้ปัญหาทั่วไป เช่น การเรียก API การประมวลผลข้อมูลจากฐานข้อมูล และบางทีที่สำคัญที่สุด — ทำให้โค้ดของเราเป็นระเบียบและสอดคล้องกัน เพื่อให้ผู้คนมีเวลาพยายามทำความเข้าใจโค้ดของเราได้ง่ายขึ้น ซีรีส์นี้มีวัตถุประสงค์เพื่อแบ่งปันโครงการสำเร็จรูปที่ฉันสร้าง ใช้ และบำรุงรักษาในช่วงสองปีที่ผ่านมา ไม่เพียงเท่านั้น ฉันจะแบ่งปันวิธีการเขียนโค้ด ตลอดจนวิธีแก้ไขปัญหาทั่วไปที่อาจเกิดขึ้น จากที่กล่าวมานี่คือตอนที่ 1: สำรวจโครงการ

ดูบน Github

สถาปัตยกรรมโครงการ

1. คุณสมบัติ

คุณสมบัติที่สร้างขึ้นในโครงการนี้:

  • สถาปัตยกรรม: สถาปัตยกรรมที่สะอาด
  • การจัดการสถานะ: flutter_bloc
  • การนำทาง: auto_route
  • DI: get_it ฉีดได้
  • REST API: dio
  • GraphQL: อาร์ทิมิส , graphql_flutter
  • ฐานข้อมูล: objectbox
  • ค่ากำหนดที่ใช้ร่วมกัน: encrypted_shared_preferences
  • ชั้นข้อมูล: แช่แข็ง
  • ผ้าสำลี: dart_code_metrics , flutter_lints
  • CI/CD: Github Actions, Bitbucket Pipelines
  • การทดสอบหน่วย: ม็อกเทล , bloc_test
  • เพจ: infinite_scroll_pagination
  • ยูทิลิตี้ : rxdart , dartx , async
  • ตัว สร้างเนื้อหา: flutter_gen_runner , flutter_launcher_icons , flutter_native_splash
  • คุณสมบัติอื่นๆ: คำขอ API ที่ล้มเหลวในการลองใหม่อัตโนมัติ, รีเฟรชโทเค็น,...

ในการรันโปรเจ็กต์นี้ตามที่ตั้งใจไว้ เวอร์ชั่น Flutter ของคุณต้องเป็น (ตรงเป๊ะ) 3.3.9 และMelos เวอร์ชั่น 2.8.0จะต้องติดตั้ง หากคุณต้องการมี Git Convention สำหรับทีมของคุณ คุณอาจติดตั้งlefthook ฉันจะพูดถึง Melos และ Lefthook ในตอนหลังของซีรีส์นี้ สำหรับตอนนี้ ต่อไปนี้เป็นวิธีการเรียกใช้โปรเจ็กต์

หมายเหตุ: ฉันใช้ VSCode และ macOS ดังนั้นฉันจึงไม่สามารถรับประกันได้ว่ามันจะทำงานบน Android Studio หรือ Windows ด้วย

2.1. การติดตั้งเมลอส

Clean Architecture ต้องการให้เราแบ่งโครงการออกเป็นหลายโมดูล (แพ็คเกจ) ซึ่งเป็นเหตุผลว่าทำไมเราจึงต้องการเครื่องมือสำหรับจัดการแพ็คเกจ — Melos ในการติดตั้ง Melos:

dart pub global activate melos 2.8.0

export PATH="$PATH:<path to flutter>/flutter/bin"
export PATH="$PATH:<path to flutter>/flutter/bin/cache/dart-sdk/bin"
export PATH="$PATH:~/.pub-cache/bin"

หลังจากติดตั้ง Melos แล้ว คุณจะต้องเรียกใช้คำสั่งmake gen_envหรือdart run tools/gen_env.dart. นี่เป็นเครื่องมือที่ฉันสร้างขึ้นเอง เพื่อสร้างenvโฟลเดอร์เพื่อให้เราสามารถประกาศความลับ เช่น Google API_KEY, ข้อมูลรับรองความถูกต้องพื้นฐาน ฯลฯ เครื่องมือนี้จะอ่านค่าใน.envไฟล์ แล้วใส่ลงในdart-defineแฟล็กเมื่อแอพ Flutter วิ่งหรือสร้าง

โฟลเดอร์ Env และไฟล์ .env ที่สอดคล้องกับสภาพแวดล้อม

2.3. กำลังสร้างไฟล์

เมื่อเราได้envโฟลเดอร์แล้ว เราก็แค่เรียกใช้คำmake syncสั่ง นี่คือชวเลขสำหรับสามคำสั่ง:

melos bootstrap
melos run l10n
melos run force_build_all

ถัดไป คำสั่งmelos run l10nช่วยสร้างคลาสSจาก.arbไฟล์ในresourcesโมดูล

สุดท้าย คำสั่งmelos run force_build_allช่วยสร้างไฟล์.g.dart, .freezed.dartฯลฯ บนแพ็คเกจทั้งหมดที่มีbuild_runnerไลบรารี

ตอนนี้เรียกmake run_devใช้แอพและสนุกได้เลย!

3. สถาปัตยกรรมโครงการ

สรุปแล้วเรามี 6 โมดูล (แพ็คเกจ)

  • แอป
  • โดเมน
  • ข้อมูล
  • ทรัพยากร
  • ใช้ร่วมกัน
  • โปรแกรมเริ่มต้น

3.1. โมดูลแอพ

นี่คือที่ที่เราจะเขียนโค้ด UI และ Bloc นอกจากนี้ยังเป็นที่ที่เราจะจัดเก็บทรัพยากรของเรา เช่น สี มิติ และลักษณะข้อความ นี่คือที่ที่เราจะประกาศฟังก์ชัน main() ของเราเป็นจุดเริ่มต้นเพื่อให้แอปของเราทำงาน

โมดูลแอป

ทั้งสองโฟลเดอร์app_iconและsplashมีไฟล์ เรา app-icon.yamlจึงsplash.yamlสามารถกำหนดค่าไอคอนแอปและหน้าจอสแปลชได้ตามคำแนะนำของไลบรารีflutter_launcher_iconsและflutter_native_splash

appโฟลเดอร์ประกอบด้วยซึ่งAppBlocเป็น Bloc ที่จัดเตรียมไว้ในMaterialAppที่ซึ่งหน้าจอทั้งหมดในแอปสามารถดึงข้อมูลออกมาได้ หรืออีกนัยหนึ่งคือ มันเหมือนกับ Bloc ที่แบ่งปันไปยังทุกหน้าจอ คุณสามารถใช้AppBlocสำหรับงานที่ต้องอัปเดต UI ของหน้าจอทั้งหมด เช่น การตั้งค่าภาษา การสลับโหมดมืด/โหมดสว่าง

โฟลเดอร์คือ ที่ ที่เราจะเขียนโค้ดคลาส พื้นฐานbaseเช่นBasePageState, BaseBloc, BaseEventและBaseState

โฟลเดอร์shared_viewนี้เป็นที่ที่ฉันจะเขียนโค้ด UI ที่ใช้ร่วมกันในหลายหน้าจอ จากนั้นจึงจะนำไปใช้กับโปรเจ็กต์อื่นๆ ได้ ตัวอย่างเช่น: app_text_fieldเป็นcircle_imageมุมมองกลุ่มทั่วไปที่เห็นในหน้าจอเข้าสู่ระบบและลงทะเบียน

โฟลเดอร์common_viewนี้เป็นที่ที่ฉันจะเขียนโค้ด UI ที่ใช้ร่วมกันในหลายหน้าจอ จากนั้นจึงจะนำไปใช้กับโปรเจ็กต์อื่นๆ ได้ ตัวอย่างเช่น , common_dialog, common_app_barสามารถcommon_scaffoldใช้ซ้ำได้ในหลายโปรเจ็กต์ แม้ว่าเราอาจต้องการปรับแต่งเล็กน้อยเพื่อให้ตรงกับสไตล์

โฟลเดอร์configคือที่ที่ฉันจะเรียกใช้ฟังก์ชันการกำหนดค่าสำหรับโมดูลแอป เช่นFirebase.initializeApp()

โฟลเดอร์diนี้มีไว้สำหรับการตั้งค่า DI สำหรับโมดูลแอป

โฟลเดอร์exception_handlerจะดูแลข้อยกเว้นทั้งหมดในโครงการ

โฟลเดอร์helperมีคลาส Helper ที่ใช้สำหรับโมดูลแอปโดยเฉพาะ

โฟลเดอร์navigationคือที่ที่เราจะประกาศหน้าจอตลอดจนกล่องโต้ตอบและแผ่นงานด้านล่างที่ใช้ในแอป

โฟลเดอร์resourceคือที่ที่เราจะประกาศทรัพยากรสำหรับ UI เช่น สี มิติ และลักษณะข้อความ

โฟลเดอร์uiคือที่ที่ฉันจะเขียนโค้ด UI และ Bloc สำหรับแต่ละหน้าจอ

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

3.2. โมดูลโดเมน

โมดูลโดเมน

เช่นเดียวกับโมดูลแอป โฟลเดอร์configและdiให้บริการตามวัตถุประสงค์ที่คล้ายกันในโมดูลโดเมน

โฟลเดอร์entityประกอบด้วยคลาสเอนทิตี เช่น ผู้ใช้และการจอง

โฟลเดอร์navigationมีAppNavigatorคลาสซึ่งรับผิดชอบpush, replace, และpopหน้าจอ

โฟลเดอร์repositoryคือตำแหน่งที่เราจะประกาศคลาส Abstract Repository ในโครงการ

โฟลเดอร์usecaseคือที่ที่เราจะประกาศ Use Cases ในโครงการ

3.3. โมดูลข้อมูล

โมดูลข้อมูล

ในทำนองเดียวกันโฟลเดอร์configและdiให้บริการเพื่อจุดประสงค์เดียวกันที่นี่

โฟลเดอร์graphqlประกอบด้วย.graphqlและschema.graphqlถ้าโครงการของคุณใช้ GraphQL ไฟล์build.yamlนี้ใช้สำหรับกำหนดค่าไฟล์ที่สร้างขึ้นซึ่งเกี่ยวข้องกับ GraphQL

โฟลเดอร์repositoryมีคลาส Repository Implementation นอกจากนี้ยังมีหลายโฟลเดอร์:

  • converter: สำหรับคลาสตัวแปลงการเข้ารหัส
  • mapper: สำหรับการเข้ารหัสคลาส Mapper ซึ่งจะใช้ในการแมปแบบจำลองข้อมูลเข้ากับเอนทิตี
  • model: สำหรับคลาสโมเดลข้อมูลการเข้ารหัส
  • source: สำหรับการเข้ารหัสคลาสพื้นฐานเช่น RestApiClient, GraphQlApiClient รวมถึงการจัดหาแหล่งข้อมูลในโครงการเช่น AppApiService, AppDatabase, AppSharedPreferences, LocationDataSource
โมดูลทรัพยากร

นี่เป็นโมดูลที่เรียบง่าย มีเฉพาะ.arbไฟล์ที่มีสตริงสำหรับการแปลเป็นภาษาท้องถิ่น โมดูลนี้แทรกเข้าไปในโมดูลอื่นอีกสองโมดูลappดังนั้นdomainคลาส UI, Bloc, Entity และ Use Case จึงสามารถใช้ได้

3.5. โมดูลที่ใช้ร่วมกัน

โมดูลที่ใช้ร่วมกัน

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

3.6. โมดูล Initializer

โมดูลตัวเริ่มต้น

โมดูลนี้มีเฉพาะคลาส AppInitializer คลาสนี้รับผิดชอบในการรวบรวมการกำหนดค่าทั้งหมดจากโมดูลอื่นในinit()ฟังก์ชัน ฟังก์ชันmain()จะต้องเรียกใช้init()ฟังก์ชันนี้เพื่อดึงการกำหนดค่าทั้งหมดจากโมดูลทั้งหมดเท่านั้น

3.7. โฟลเดอร์และไฟล์อื่นๆ

โฟลเดอร์.githubนี้มีไว้สำหรับกำหนดค่า CI/CD หากโปรเจ็กต์ของคุณใช้ Github Actions

โฟลเดอร์.lefthookและไฟล์lefthook.ymlมีไว้สำหรับกำหนดค่าแบบแผน Git โดยใช้ไลบรารีของ lefthook

โฟลเดอร์.vscodeนี้มีไว้สำหรับประกาศการตั้งค่า VSCode ที่ใช้สำหรับโครงการนี้

โฟลเดอร์.envมีไว้สำหรับประกาศความลับสำหรับแต่ละสภาพแวดล้อม

โฟลเดอร์toolsมีเครื่องมือที่ฉันสร้างขึ้น

ไฟล์analysis_options.yamlนี้มีไว้สำหรับประกาศกฎ linter ของสองไลบรารีflutter_lintsและdart_code_metrics

ไฟล์bitbucket-pipelines.ymlนี้มีไว้สำหรับกำหนดค่า CI หากโครงการของคุณใช้ Bitbucket Pipelines

ไฟล์makefileติดตามคำสั่งทั้งหมดที่ใช้ในโครงการ ตัวอย่างเช่น คำสั่งmake formatช่วยจัดรูปแบบรหัสทั้งหมดที่มี.dartนามสกุลไฟล์ภายในโครงการ มีคำสั่งอื่นๆ อีกมากมายmake testที่เรียกใช้การทดสอบหน่วยในทุกโมดูล

ไฟล์melos.yamlนี้ใช้สำหรับกำหนดค่า Melos

บทสรุป

ทำไมเราต้องสร้างโมดูลอื่นสำหรับการกำหนดค่าเริ่มต้น คลาสตัวทำแผนที่ เอนทิตี และกรณีการใช้งานคืออะไร ฉันจะพูดถึงสิ่งเหล่านี้ในส่วนที่สองของซีรี่ส์นี้ — Clean Architecture