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.
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.
İlk önce, menünün gerçekte ne yapmasını istediğimizi düşünelim:
Bir kullanıcı bir menü düğmesine dokunduğunda menü görüntülenmelidir.
Dokunarak bir route'a giden her sayfa için bir menü item olmalıdır.
Uygulama bilgilerini içeren bir modal gösteren bir About menü item olmalıdır.
Kullanıcı bilgilerini gösteren bir menü başlığı olmalıdır. Kullanıcı ayarlarına dokunduğunuzda, user settings sayfasına yönlendirilmelidir.
Menü, o anda hangi route'ın aktif olduğunu vurgulamalıdır.
Bir menü item seçildiğinde veya bir kullanıcı menünün sağındaki menü katmanına dokunduğunda menü kapanmalıdır.
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.
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!"),
],
),
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:
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,
);
_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 (), 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
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
Top comments (0)