Flutter'da Yönlendirme (Routing) 💫 🌌 ✨
Tags: flutter, dart, routing, mobile
Published: 2022-04-10 11:54:36

**Flutter'da Yönlendirme**
Flutter'da sayfalar yalnızca rota(route)'lara atadığımız widget'lardır ve route'lar bir widget olan `Navigator` tarafından yönetilir. 

_Not: Flutter'da routing(yönlendirme) hiçbir zaman gerçekten statik değildir, ancak tüm rotalarınızı önceden bildirebilirsiniz._

**Declarative routing and named routes (Bildirime dayalı yönlendirme ve adlandırılmış route'lar)**

Her RouteDefinition'ın bir yolu(path) ve bir component'i vardır (bu muhtemelen bir sayfadır). Bu genellikle bir uygulamanın en üst düzeyinde yapılır. 

Mobil uygulamalar genellikle onlarca sayfayı destekler ve uygulamanın her yerinde adsız yollar oluşturmak yerine, onları bir kez tanımlayıp ardından adlarına göre başvurmak daha kolay bir yoldur.


**Declaring routes**

Adlandırılmış route'ları kullanmanın iki bölümü vardır. Birincisi route'ları belirlemektir.

```
return MaterialApp(
  debugShowCheckedModeBanner: false,
  theme: _theme,
  routes: {
    ECommerceRoutes.catalogPage: (context) =>
        PageContainer(pageType: PageType.Catalog),
    ECommerceRoutes.cartPage: (context) =>
        PageContainer(pageType: PageType.Cart),
    ECommerceRoutes.userSettingsPage: (context) =>
        PageContainer(pageType: PageType.Settings),
    ECommerceRoutes.addProductFormPage: (context) =>
        PageContainer(pageType: PageType.AddProductForm),
  },
  navigatorObservers: [routeObserver],
);
 
class ECommerceRoutes {
  static final catalogPage = '/';
  static final cartPage = '/cart';
  static final userSettingsPage = '/settings';
  static final cartItemDetailPage = '/itemDetail';
  static final addProductFormPage = '/addProduct';
}

```


**Adlandırılmış rotalarda gezinme:**
Adlandırılmış rotalara gitmek, `Navigator.pushNamed` methodunu kullanmak kadar kolaydır. `pushNamed` methodu bir `BuildContext` ve bir `route adı` gerektirir, böylece BuildContext'e erişim olan her yerde kullanabilir. 


`final value = await Navigator.pushNamed(context, "/cart");`

Navigator sayfaları bir "stack" yapısında yerleştirir. Stack "last in first out" (son giren ilk çıkar ilkesine göre çalışır). Uygulamanızın home page'ine bakıyorsanız ve yeni bir sayfaya giderseniz, o yeni sayfayı stack'in üstüne (ve ana sayfanın en üstüne) itersiniz. Stack'deki en üst öğe, ekranda gördüğünüz şeydir.



_Navigator, `stack` benzeri bir yapıdır._
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phl9lun0luq88wpcwncy.png)
 

`Navigator` sınıfı, `stack`'i yönetmek için bir dizi yararlı yönteme sahiptir.

• `pop`
• `popUntil`
• `canPop`
• `push`
• `pushNamed`
• `popAndPushNamed`
• `replace`
• `pushAndRemoveUntil`


Adlandırılmış route'ları pushlamak'la ilgili önemli bir not, bir Future döndürmeleridir. `await` anahtar sözcüğü asenkron(eş zamansız) bir değer döndüren ifadeleri işaretlemek için kullanılır.


```
onPressed: () {
    return Navigator.of(context).pushNamed("/cartPage");
},
```

_**Not:** Navigator.of(context).pushNamed(String routeName) fonksiyon imzasının, daha önce bahsedilen Navigator.pushNamed(BuildContext context, String routeName) imzasıyla aynı olmadığını fark edebilirsiniz. Bunlar değiştirilebilir._


**MaterialDrawer widget'ı ve full menü**
Bir `Materyal Design` uygulaması gördüyseniz, muhtemelen aşağıdaki resim de gösterilen app drawer türünü biliyorsunuzdur.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zoe4687z1art63l03kwz.png)
 
İlk önce, menünün gerçekte ne yapmasını istediğimizi düşünelim:

1. Bir kullanıcı bir menü düğmesine dokunduğunda menü görüntülenmelidir.

2. Dokunarak bir route'a giden her sayfa için bir menü item olmalıdır.

3. Uygulama bilgilerini içeren bir modal gösteren bir About menü item olmalıdır.

4. Kullanıcı bilgilerini gösteren bir menü başlığı olmalıdır. Kullanıcı ayarlarına dokunduğunuzda, user settings sayfasına yönlendirilmelidir.

5. Menü, o anda hangi route'ın aktif olduğunu vurgulamalıdır.

6. Bir menü item seçildiğinde veya bir kullanıcı menünün sağındaki menü katmanına dokunduğunda menü kapanmalıdır.

7. Menü açıldığında veya kapatıldığında, güzel bir şekilde içeri ve dışarı hareket etmelidir.


Bu özel menü drawer, tümü Flutter'da yerleşik olarak bulunan yalnızca beş gerekli widget'ın birleşimidir:

• `Drawer`
• `ListView`
• `UserAccountsDrawerHeader`
• `ListTile`
• `AboutListTile`

`Drawer`, bu menüyü barındıran widget'tır. Alt argümanında tek bir widget alır. Bir `Drawer` , büyük olasılıkla drawer argumanında bir `Scaffold`'a iletilecektir.


Ayrıca drawer'lı bir scaffold'da bir AppBar'ınız varsa, Flutter app bar'da sağ tarafta bulunan simgeyi otomatik olarak bir menü butonuna ayarlar ve dokunulduğunda menüyü açar. Menü güzel bir şekilde canlandırılacak ve sola kaydırdığınızda veya sağdaki butona dokunduğunuzda kapanacaktır.


**Menü öğeleri ve uygun widget'lar: `ListView` ve `ListItems`**

_**Not:** Scaffold'un `.automaticallyImplyLeading` öğesini `false` olarak ayarlayarak otomatik menü butonunu geçersiz kılabilirsiniz._


`ListView`, widget'ları kaydırılabilir bir `container`'da düzenleyen bir `layout widget`'ıdır. Çocuklarını varsayılan olarak dikey(vertically) olarak düzenler, ancak oldukça özelleştirilebilirdir. Child adında bir argüman bekleyen diğer genelleştirilmiş widget'ların aksine, ListTile `title`, `subtitle` ve `leading` (satır başı) gibi özelliklere sahiptir ve ayrıca bir `onTap` özelliği ile donatılmıştır.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ji59u8dq6907xdbv3sf2.png)
 

Bir ListView'i daha "materyal-vari" bir menü drawer'a dönüştürmek için özel olarak kullanılan başka bazı özel Flutter widget'ları da vardır.
 
Örnek AppMenu widget'ı oluşturma methodu:
```
@override
  Widget build(BuildContext context) {
    _activeRoute ??= "/";
    return Drawer(
      child: ListView(
        children: <Widget>[
          StreamBuilder( //bir child beklemek yerine builder modelini izler
            // ...
            builder: (
              BuildContext context,
              AsyncSnapshot<ECommerceUser>
            ) => UserAccountsDrawerHeader(),
          ), // StreamBuilder
          ListTile(
            leading: Icon(Icons.apps),
            title: Text("Catalog"),
            selected: _activeRoute == ECommerceRoutes.catalogPage,
            onTap: () => _navigate(ECommerceRoutes.catalogPage),
          ),
          ListTile(...),
          ListTile(...),
          AboutListTile(...),
        ],
      ),
    );
  }

```

`UserAccountsDrawerHeader`, önemli kullanıcı bilgilerini görüntülemek için kullanılan bir `Material` widget'ıdır. Butona dokunarak kullanıcı hesapları arasında geçiş yapmanızı sağlayan GMail gibi bir Google uygulaması hayal edin. Bu GMail benzeri kullanıcı arayüzünü, `UserAccountsDrawerHeader` kullanarak elde edebilirsiniz.


`AboutListTile` widget'ı `ListView.children` listesine aktarılabilir ve bu aşağıdaki listede gösterildiği gibi yalnızca birkaç satır kodla yapılandırılabilir.


```
AboutListTile(
  icon: Icon(Icons.info),
  applicationName: "Produce Store",
  aboutBoxChildren: <Widget>[
    Text("Thanks for reading Flutter in Action!"),
  ],
),
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2o5vrzuvc2vbe6d2mzgh.png)
 

**Navigator.pushReplacementNamed**

`Navigator.pushReplacementNamed`, rota yığınının yeni sayfalar eklemeye devam etmemesini sağlar.

Yeni rotanın animasyonu bittiğinde, navigasyon yaptığınız rotayı kaldırır.

İletişim kutuları veya popup menüler gibi rotalar, kullanıcı tarafından seçilen değeri rotasını oluşturan widget'a döndürmek için genellikle bu mekanizmayı kullanır.


```
void _switchToBrightness() {
  Navigator.pushReplacementNamed(context, '/settings/brightness');
}
```

**NavigatorObserver: RouteAware ile aktif rotayı vurgulama**

`Navigator observer`, onu dinleyen herhangi bir widget'a "Hey, eğer ilgileniyorsanız, Navigator bir olay gerçekleştiriyor" diyen bir nesnedir.

`RouteObserver` ise NavigatorObserver'ın bir alt sınıfıdır. 
Bu observer(gözlemci), aktif route değişirse tüm dinleyicilerini bilgilendirir.

 

```
final RouteObserver<Route> routeObserver =
    RouteObserver<Route>();
 
class ECommerceApp extends StatefulWidget {
  @override
  _ECommerceAppState createState() => _ECommerceAppState();
}
 
```



Route observer'larını MaterialApp widget'ına iletin:

```
return MaterialApp(
    debugShowCheckedModeBanner: false,
    theme: _theme,
    home: PageContainer(pageType: PageType.Catalog,),
    routes: { ... }
    navigatorObservers: [routeObserver],
);
```

Artık observer'ı herhangi bir `State` nesnesinde dinleyebilirsiniz.

State nesnesinin burada gösterildiği gibi RouteAware ile genişletilmesi gerekir:

```
class AppMenu extends StatefulWidget {
  @override
  AppMenuState createState() => AppMenuState();
}
 
class AppMenuState extends State<AppMenu>
    with RouteAware { ... }

```
`RouteAware` , bir `route observer` ile etkileşim kurmak için arabirim sağlayan soyut(abstract) bir sınıftır. Artık state nesnenizin `didPop`, `didPush` ve diğer birkaç methoda erişimi vardır.

Menüyü aktif route ile güncellemek için stack'de yeni bir sayfa olduğunda bilgilendirilmemiz gerekiyor. Bunun iki adımı vardır: ilk adım, route observer'dan değişiklikleri dinlemek, ikinci adımsa, route değiştiğinde haberdar olmak için observer'ı dinlemektir.


```
class AppMenuState extends State<AppMenu> with RouteAware {
  String _activeRoute;
  UserBloc _bloc;
 
    @override
    void didChangeDependencies() {
        super.didChangeDependencies();
        routeObserver.subscribe(
          this,
          ModalRoute.of(context),
        );
        _bloc = AppStateContainer.of(context)
            .blocProvider.userBloc;
    }
}
```

Artık bu widget, rota değişikliklerinin farkında olduğundan, herhangi bir Navigator etkinliği gerçekleştiğinde aktif rota değişkenini güncellemesi gerekir. Bu, RouteAware'den devralınan `didPush` yönteminde yapılır:

```
@override
void didPush() {
 
  _activeRoute =
    ModalRoute.of(context).settings.name;
 
}

```

**Anında yönlendirme(Routing on the fly)**

`Anında yönlendirme`, bir olaya yanıt olarak oluşturulana kadar var olmayan bir sayfaya yönlendirme yapabileceğiniz fikridir. Örneğin, kullanıcı bir liste öğesine dokunduğunda yeni bir sayfaya gitmek isteyebilirsiniz. Bu rotayı önceden oluşturmanız gerekmez, çünkü rotalar yalnızca widget'lardır.

```
void _showListItemDetailPage() async {
    await Navigator.push(//adlandırılmış route'lara Navigator.pushNamed aracılığıyla gidilir
      context,
      MaterialPageRoute(
        builder: (context) => SettingsPage(
              settings: settings,
            ),
        fullscreenDialog: true, //tam ekran
      ),
    );
  }
```

Flutter'da, `route stack`'de yeni bir widget gibi görünen her şey bir rotadır. `Modallar`,  `bottom sheet`'ler, `snack bar`'lar ve `diyalog`'ların tümü rotalardır ve bunlar anında yönlendirme için mükemmel adaylardır.

**MaterialRouteBuilder**


```
Future _toProductDetailPage(Product product) async {
    await Navigator.push(//stack'e yeni bir sayfa eklemek için Navigator.push'u kullanabilirsiniz.
        context,
        MaterialPageRoute(//MaterialPageRoute, PageRoute'un bir alt sınıfıdır ve tüm Material widget işlevselliğini widget tree'deki yeni yerinde sağlar.
            builder: (context) => //MaterialPageRoute gibi route nesneleri, bir callback alan ve widget döndüren bir builder argument değişkeni gerektirir.
            ProductDetailPageContainer(
              product: product,
            ),
        ),
    );
}\
```

**showSnackBar, showBottomSheet ve benzerleri**

Flutter, modallar ve snackbarlar gibi sayfa olmayan route'ları kullanmayı çok kolaylaştıran widget'lara ve mantığa sahiptir. Bunlar bir sayfaya eklenmek yerine Navigator stack'ine eklenen widget'lardır.

Bazı route'lar ekranın tamamını kaplamaz:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0gh42u2jg8usme8xh5s.png)
 
Bu uygulamada, bottom sheet (iOS uygulamalarında yaygın olan) ve bir snackbar kullanıyoruz. Bunlar, ekranın altından görünmeleri ve ekranın yalnızca bir bölümünü kaplamaları bakımından benzerdir. Yine de farklılardır çünkü bottom sheet bir `ModalRoute`'dır. Yani, görüntülendiğinde, altındaki sayfayla etkileşime giremezsiniz. Snack bar, uygulamayı engellemez, bu nedenle görünümdeyken etkileşimde bulunmaya devam edebilirsiniz.



Aşağıdaki örnekte bottom sheet aynı Catalog widget'ında uygulanır ve `ProductDetailCard.onLongPress` methodu aracılığıyla bir ProductDetailCard'a basılı tutularak başlatılır:
```
return ProductDetailCard(
    key: ValueKey(_product.imageTitle.toString()),
    onTap: () => _toProductDetailPage(_product),
    onLongPress: () =>
        _showQuickAddToCart(_product), //Uzun bir basışta, bottom sheet'i gösterir.
    product: _product,
);

```

Bottom sheet widget örneği:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/549tcmovxnni68qqnib9.png)
 
_showQuickAddToCart methodu:

```
void _showQuickAddToCart(BuildContext context, Product product) async {
  CartBloc _cartBloc = AppState.of(context).blocProvider.cartBloc;
 
  int qty = await showModalBottomSheet<int>(//1
 
    context: context, //2
 
    builder: (BuildContext context) { //3
        return AddToCartBottomSheet(
            key: Key(product.id),
        );
    });
 
    _addToCart(product, qty, _cartBloc);
}
```

1: `showModalBottomSheet`, Flutter tarafından sağlanan ve sizin için yönlendirmeyle(routing) ilgilenen global bir methoddur. Tür bildirimi (<int>), bottom sheet'den ne tür verilerin geri gönderilmesini bekleyebileceğinizi söyler. Bu satır ayrıca showModalBottomSheet'in dönüş değerine bir değer atar. Method bir Future döndürdüğünden, wait anahtar sözcüğünü kullanmanız gerekir. Future, temel olarak, "Kullanıcı bottom sheet'i kapatır kapatmaz size doğru değeri vereceğim ve doğru değeri alacağım" der.

2: Tüm rotaların bir `BuildContext`'e ihtiyacı vardır, böylece Flutter bunları ağaçta nereye ekleyeceğini bilir.

3: Tüm rota değiştirme methodları, widget döndüren bir callback bekler


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8d32i7dg67afp5s9f1uf.png)
 

```
class AddToCartBottomSheet extends StatefulWidget {
  const AddToCartBottomSheet({Key key}) : super(key: key);
 
  @override
  _AddToCartBottomSheetState createState() => _AddToCartBottomSheetState();
}
 
class _AddToCartBottomSheetState extends State<AddToCartBottomSheet> {
  int _quantity = 0;
  // ...
 
  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: BoxConstraints(
        minWidth: MediaQuery.of(context).size.width,
        minHeight: MediaQuery.of(context).size.height / 2,
      ),
      child: Column(
        children: <Widget>[
          Padding(
            // ...
            child: Text("Add item to Cart"),
          ),
          Padding(
            // ...
            child: Row(
              children: <Widget>[
                IconButton(...) // decrease qty button
                Text(...) // current quanity
                IconButton(...) // increase qty button
              ],
            ),
          ),
          RaisedButton(
            color: AppColors.primary[500],
            textColor: Colors.white,
            child: Text(
              "Add To Cart".toUpperCase(),
            ),
            onPressed: () =>
              Navigator.of(context).pop(_quantity),
          )
        ],
      ),
    );
}


```

**Yönlendirme animasyonları**

Sayfalar yalnızca widget'lardır, bu nedenle diğer widget'lar gibi canlandırılabilirler.
Sayfaların, platforma göre farklılık gösteren varsayılan geçişleri vardır: iOS style ya da Material style.
Tüm geçişler, biri görüntülenmekte olan ve biri görüntüden çıkmakta olan iki sayfa içerir.

Geçişler `PageRoute` tarafından veya PageRoute'u genişleten `MaterialPageRoute`, `ModalRoute`'u ve `TransitionRoute`'u genişleten `MaterialPageRoute` tarafından işlenir. Bu karmaşanın bir yerinde, diğer şeylerin yanı sıra argüman olarak iki animasyon alan `buildTransitions` adlı bir method vardır. Birisi çıkarken kendisi içindir, ikincisi ise onun yerini alan rota ile koordinelidir. `MaterialPageRoute` zaten geçişleri uygular, bu da `MaterialPageRoute.buildTransitions`'ı override edebileceğiniz anlamına gelir.

Bu, animasyonlu bir widget'a sarılmış sayfayı basitçe döndürebileceğiniz ve sayfanın buna göre canlandıracağı anlamına gelir.


```
class FadeInSlideOutRoute<T> extends
    MaterialPageRoute<T> {
  FadeInSlideOutRoute({WidgetBuilder builder, RouteSettings settings})
      : super(builder: builder, settings: settings);
 
  @override
  Widget buildTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    if (settings.isInitialRoute) return child;
    if (animation.status == AnimationStatus.reverse) {
      return super.buildTransitions(
        context,
        animation,
        secondaryAnimation,
        child,
      );
    }
    return FadeTransition(
        opacity: animation,
        child: child,
    );
  }
}


```

Özet:

• Flutter, yönlendirmeyi çok daha esnek ve akıcı hale getiren dinamik yönlendirme (routing) kullanır.

• Flutter's Navigator, tıpkı bazı kullanıcı etkileşimleri gerçekleşirken veya uygulama yeni veriler alırken, kodunuzda "anında" rotalar oluşturmanıza olanak tanır.

• Navigator, bazı kullanıcı etkileşimleri gerçekleşirken veya uygulama yeni veriler alırken, kodunuzda "anında" (on the fly) rotalar oluşturmanıza olanak tanır.

• Flutter, adlandırılmış route'ları kullanarak statik yönlendirmeyi destekler.

• Navigator, tüm rotaları yığın(stack) şeklinde yönetir.

• Rotalara yönlendirme işi, `Navigator.push` ve `Navigator.pop` methodları çağrılarak yapılır.

• `Navigator.push` çağrıları, yeni rota tarafından geri aktarılacak bir değeri bekleyen bir Future döndürür.

• Flutter'da Material-style menu drawer'ı oluşturmak birkaç widget gerektirir: `Drawer`, `ListView`, `ListTile`, `AboutListTile` ve `DrawerHeader`.

• Bir RouteObserver kurarak ve herhangi bir widget'ın state nesnesine abone olarak yönlendirmedeki değişiklikleri tahmin edebilirsiniz.

• Birkaç UI öğesi Navigator ile yönetilir ve teknik olarak rotalardır, ancak bunlar snackbarlar, alt sayfalar, çekmeceler ve menüler gibi sayfalar değildir.

• GestureDetector widget'ını kullanarak kullanıcı etkileşimlerini dinleyebilirsiniz.

• Özel sayfa geçişlerini uygulamak, Route sınıflarını extend ederek yapılır.



Resources: 
•Flutter in Action Chapter: 7
•https://api.flutter.dev/flutter/widgets/Navigator/pushReplacementNamed.html
•https://api.flutter.dev/flutter/widgets/RouteObserver-class.html