# Navigate โดยไม่ต้องมี Context

#Dev#Flutter

คือโดยปกติแล้วเวลาเราต้องการเปลี่ยนหน้าในแอปของเรา เราจะต้องใช้ context ในการควบคุมการเปลี่ยนหน้า และในบทความนี้เราจะมีสอนการทำแบบไม่ต้องใช้ context กัน เช่น Navigator.of(context).push(...) ที่ใช้ในการเปลี่ยนไปยังหน้า... ซึ่งผมไปอ่านบทความจากอันนี้มาอีกทีเห็นว่ามีประโยชน์ดีเลยเอามาเขียนครับ Navigate without context in Flutter with a Navigation Service (opens new window)

Header

# สร้างโปรเจค

flutter create global_navigation

จากนั้นก็ลอกโค้ดด้านล้างนี้ไปใส่ใน lib/main.dart ครับ

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // กำหนด Route ในการเปลี่ยนหน้าแอป
      onGenerateRoute: (settings) {
        switch (settings.name) {
          case 'about':
            return MaterialPageRoute(builder: (context) => AboutPage());
          case 'home':
          default:
            return MaterialPageRoute(builder: (context) => HomePage());
        }
      },
      // กำหนดให้เริ่มต้นที่หน้า home
      initialRoute: 'home',
    );
  }
}

class HomePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text(
              'Home',
              style: TextStyle(
                fontSize: 18.0,
                fontWeight: FontWeight.bold,
              ),
            ),
            RaisedButton(
              child: Text('Go to about'),
              onPressed: () {
              },
            )
          ],
        ),
      ),
    );
  }
}

class AboutPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text(
              'About',
              style: TextStyle(
                fontSize: 18.0,
                fontWeight: FontWeight.bold,
              ),
            ),
            RaisedButton(
              child: Text('Go to home'),
              onPressed: () {
              },
            )
          ],
        ),
      ),
    );
  }
}

ลองรันดูก็จะพบกับหน้าแอปแบบนี้ครับ

Title
หน้าแอปเริ่มต้น

ซึ่งจากโค้ดด้านบนจะยังไปหน้าอื่นไม่ได้ครับ (ยังไม่ได้เขียน)

# ลองทำ Navigate แบบปกติดูก่อน

เพิ่ม Navigator แบบที่ยังต้องใช้ context กันดูก่อนครับ





 






 



...
RaisedButton(
  child: Text('Go to about'),
  onPressed: () {
    Navigator.of(context).pushReplacementNamed('about');
  },
)
...
RaisedButton(
  child: Text('Go to home'),
  onPressed: () {
    Navigator.of(context).pushReplacementNamed('home');
  },
)

ลองกดปุ่มในแอปเล่นดูก็จะพบว่าสามารถเปลี่ยนหน้าได้แล้ว

Note

Navigator.of(context).pushReplacementNamed(...) จะเป็นการเปลี่ยนหน้าแบบทับหน้าเดิม ถ้าเราอยากเปลี่ยนหน้าโดยกลับมาหน้าเดิมได้ให้ใช้ Navigator.of(context).push(...) แทนครับ

ทีนี้จะเกิดปัญหาอย่างนึงก็คือ ถ้าผมต้องการแยกส่วนของ onPressed ไปเป็นฟังก์ชันก็จะไม่สามารถทำได้ เพราะใน StatelessWidget จะไม่มี context ให้เราใช้ อาจจะต้องแก้เป็น StatefulWidget ก็จะทำงานได้ปกติ

void navigateTo() {
  // ไม่มี context ให้ใช้ใน StatelessWidget
  Navigator.of(context).pushReplacementNamed('about');
}

# ทำให้ Navigate จากโค้ดส่วนไหนก็ได้

ก่อนอื่นเราจะมีใช้ Library ที่มีชื่อว่า get_it (opens new window) กันครับ ซึ่งเจ้า get_it เนี่ยมีประโยชน์มากมาย แต่ในที่นี้เราจะใช้มันมาสร้าง Singleton (opens new window) กันครับ

# ติดตั้ง get_it

เพิ่ม get_it ใน pubspec.yaml



 

dependencies:
  ...
  get_it: ^3.0.3

# สร้าง Service Locator

สร้างไฟล์ lib/service_locator.dart กันครับ

import 'package:flutter/widgets.dart';
import 'package:get_it/get_it.dart';

// จะได้ Instance ของ GetIt มาซึ่งมีเพียงตัวเดียวในโปรแกรม เรียกที่ไหนก็ได้ตัวเดียวกัน
GetIt locator = GetIt.instance;

// ฟังก์ชันเริ่มต้นในการกำหนดว่าจะสร้าง Singleton หรือ อะไรบ้าง
void setupLocator() {
  // สร้าง Singleton ของ class NavigationService
  locator.registerLazySingleton(() => NavigationService());
}

class NavigationService {
  final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();

  Future<dynamic> navigateTo(String routeName) {
    return navigatorKey.currentState.pushReplacementNamed(routeName);
  }
}

# นำ Service Locator ไปใช้งาน

จากนั้นเราจะแก้ lib/main.dart ให้เรียกใช้ GetIt และ locator ของเรา
โดยเราสามารถเข้าถึง NavigationService ที่เป็น Singleton ได้ผ่านคำสั่ง locator<NavigationService>()


 


 












 








 






 




...
import 'service_locator.dart';

void main() {
  setupLocator();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // เก็บ NavigatorState ใน navigatorKey ใน NavigationService ของเรา
      navigatorKey: locator<NavigationService>().navigatorKey,
      ...
    );
  }
}
...
RaisedButton(
  child: Text('Go to about'),
  onPressed: () {
    locator<NavigationService>().navigateTo('about');
  },
)
...
RaisedButton(
  child: Text('Go to home'),
  onPressed: () {
    locator<NavigationService>().navigateTo('home');
  },
)
...

ส่วนสำคัญของโค้ดนี้ก็คือ navigatorKey ใน MaterialApp ครับ คือเมื่อเราสร้าง MaterialApp จะมีการสร้าง Navigator มาใช้งานด้วย (สามารถดูใน Source ของ MaterialApp ได้)
การที่เรากำหนดค่า navigatorKey จะทำให้เราสามารถเข้าถึง State ของ Navigator ได้โดยตรง โดยที่เราไม่ต้องใช้ Navigator.of(context) ครับ

เมื่อลองรันดูก็จะพบว่าทำงานได้เหมือนเดิม แน่นอนว่าสิ่งที่ต่างกันก็คือเราไม่ต้องใช้ context ครับ
ดังนั้นเราจึงสามารถเขียน locator<NavigationService>().navigateTo(...) ที่ไหนก็ได้ จะเอาไปใส่ใน Business Logic ก็ยังได้

ดาวน์โหลด Source Code ที่นี่ (opens new window)


อัปเดตเมื่อ: 1 ปีที่แล้ว