Flutter’da kullanılan temel widget’ların çoğunu öğrendik ve artık widget’ları ihtiyacımıza uygun bir şekilde nasıl düzenleyecemizi, ekran yerleşimlerini nasıl yapacağımızı da yeterince iyi biliyoruz. Gelin şimdi buraya kadar öğrendiklerimizi pekiştirmek için gerçek hayatta kullanılabilecek, işe yarar bir uygulama yapalım.
Dart öğrenirken birkaç kez “vücut kitle endeksi” kavramına değişmiştik. Kısaca hatırlamak gerekirse vücut kitle endeksi, kişinin kilosunun, boyun metre cinsinden değerinin karesine bölünmesiyle bulunuyordu. Örneğin, bir kişinin kilosu 90 kg ve boyu 1.85 metre ise, vücut kitle endeksi “90 / (1.85 * 1.85) = 26.29″dur. Virgülden sonra değer uzayıp gitese de biz ekranda virgülden sonra sadece iki rakam göstereceğiz. Bu değer kişinin sağlığı ile ilgili bazı bilgiler verir ve ideali 18.5 ile 25 arasında olmasıdır.
Şimdi yeni bir proje oluşturalım ve adım adım kodlamaya başlayalım. Nasıl proje oluşturacağımızı daha önce görmüştük. Projemizin ismine “vucut_kitle_endeksi” diyebiliriz. İlk iş olarak “test” klasörünü silelim. Bu klasörü neden sildiğimizi “Projeye İlk Bakış” başlığı altında açıklamıştık. Ardından yine daha önce öğrendiğimiz gibi örnek kodu silelim, “ana_sayfa” isminde ayrı bir dosya oluşturalım ve ekranımızı oluşturmaya başlayalım Eğer bu işlemleri nasıl yapacağınızı bilmiyorsanız, devam etmeden önce ilgili bölümleri tekrar gözden geçirmeniz iyi olacaktır. Bu kısımları önceki projemiz olan “ilk_flutter_projem”den kopyala-yapıştır da yapabilirsiniz. Ancak benim tavsiyem, hatırladığınız kadarıyla kodu yazmaya çalışmanız, hatırlamadığınız kısımları ise kopyala – yapıştır yapmak yerine baka baka ama tek tek elle yazmanızdır. Buna genellikle “ellerini kirletmek” denir. Hiçbir işi sadece bakarak, “ellerinizi kirletmeden” öğrenemezsiniz. Kodları tek tek elle yazmanız, konuların daha iyi aklınızda kalmasını sağlayacaktır.
İlk olarak, “AnaSayfa” içinde ekranımızı oluşturalım ve Scaffold‘a içinde başlıkla birlikte bir AppBar yerleştirelim.
ana_sayfa.dart
import 'package:flutter/material.dart'; class AnaSayfa extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Vücut Kitle Endeksi"), ), ); } }
Şimdi, alt alta bazı widget’lar yerleştireceğiz. Öyleyse Scaffold‘un “body”sine bir Column verelim ve Column içine ilk önce sonucu göstereceğimiz Text widget’ını, altına boy değerini alacağımız TextField‘ı, onun altına kilo değerini alacağımız TextField‘ı ve en alta da tıkladığımızda hesaplama yapmasını istediğimiz RaisedButton‘ı yerleştirelim.
ana_sayfa.dart
import 'package:flutter/material.dart'; class AnaSayfa extends StatelessWidget { double _sonuc = 0; TextEditingController _boyController = TextEditingController(); TextEditingController _kiloController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Vücut Kitle Endeksi"), ), body: Column( children: [ Text(_sonuc.toString()), TextField( controller: _boyController, ), TextField( controller: _kiloController, ), RaisedButton( child: Text("Hesapla"), onPressed: _hesapla, ), ], ), ); } _hesapla() {} }
Yukarıdaki kodda çok fazla işlem yapmış gibi görünüyoruz ama şimdiye kadar öğrenmediğimiz, yeni olan hiçbir şey yapmadık ve yaptığımız işlemler sadece temel işlemlerdir. Bu kısımlarda eksiğiniz varsa ilgili widget’larla ilgili ayrıntılı bölümlere bakabilirsiniz. Widget’ların daha önce anlattığımız özelliklerinin önemli bir bölümünü bu projede kullanacağız. Öncelikle, widget’ları oluştururken adım adım neler yaptığımızı inceleyelim.
En yukarıya Text widget’ını yerleştirdik. Text içindeki değeri elle girmek yerine daha önce de yaptığımız gibi dışarıda, yani sınıf kapsamında bir değişken oluşturduk ve Text widget’ına parametre olarak bu değişkenin değerini String’e çevirerek atadık. Bu değişken, hesaplayacağımız sonuç değerini tutacaktır. Ardından alt alta iki adet TextField oluşturduk ve TextField‘ların içindeki metnin değerini okuyacağımız “controller” nesnelerini oluşturup TextField‘lar ile bağladık. Son olarak en altta bir RaisedButton oluşturup tıklandığında yapılacak işlemi “_hesapla” adında ayrı bir fonksiyon olarak tanımladık ama henüz fonksiyonun gövdesini doldurmadık. Tüm widget’ları aşağıdaki ekran görüntüsünde görebilirsiniz.
Sizin de fark ettiğiniz gibi ekranımız oldukça çirkin görünüyor. Biz UI/UX uzmanı değil yazılımcıyız ama yine de bundan daha iyisini yapabiliriz. İlk olarak TextField‘ı daha önce gördüğümüz gibi “OutlineBorder” ile çevreleyelim ve Text‘in yazı boyutunu da büyütelim, nihayetinde bu kısım hesaplama sunucunu göreceğimiz kısım olacak, o yüzden de oldukça önemli.
ana_sayfa.dart
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Vücut Kitle Endeksi"), ), body: Column( children: [ Text( _sonuc.toString(), style: TextStyle(fontSize: 48), ), TextField( controller: _boyController, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), ), TextField( controller: _kiloController, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), ), RaisedButton( child: Text("Hesapla"), onPressed: _hesapla, ), ], ), ); }
Değişiklikleri yaptık ama görüntümüz hâlâ oldukça kötü duruyor. Düzenlemeye devam edelim. Öncelikle tüm öğelerimizin kenardan uzaklaşması için her taraftan Padding verelim. Bunun için de Column widget’ını Padding widget’ı ile sarmalayacağız. Bu işlemi, Column‘u komple kesip ardından “body”ye değer olarak Padding widget’ını verip sonrasında Padding‘e “child” olarak kesmiş olduğumuz Column‘u yapıştırarak yapabiliriz ama biz öyle yapmayacağız. Fareyle Column kelimesi üzerinde tıklayacağız ve çıkan “ampul” ikonunun bize sunduğu seçeneklerden “Wrap with Padding” seçeneğini seçeceğiz ve amacımıza ulaşmış olacağız. “padding” değeri otomatik olarak her yönden 8 değerini alacaktır. Biz onu silip “simetrik padding (EdgeInsets.symmetric)” ile dikeyde (üstten ve alttan) 48, yatayda (soldan ve sağdan) 16 birim boşluk oluşturacağız.
Ardından, Column içinde öğeler arasına boşluklar ekleyelim. Bunu yapma için de “Column ve Row” konusunda öğrendiğimiz gibi SizedBox widget’ını kullanabiliriz. Aşağıda, widget’lar arasına, görselin gözümüze güzel gözükmesini sağlayacak şekilde farklı yükseklik değerlerine sahip toplam üç adet SizedBox yerleştirdik.
ana_sayfa.dart
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Vücut Kitle Endeksi"), ), body: Padding( padding: EdgeInsets.symmetric(vertical: 48, horizontal: 16), child: Column( children: [ Text( _sonuc.toString(), style: TextStyle(fontSize: 48), ), SizedBox(height: 32), TextField( controller: _boyController, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), ), SizedBox(height: 16), TextField( controller: _kiloController, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), ), SizedBox(height: 32), RaisedButton( child: Text("Hesapla"), onPressed: _hesapla, ), ], ), ), ); }
Son olarak, TextField‘ların içine “Bir değer giriniz” şeklinde “labelText”ler yerleştirelim. Sonlarına da “kg” ve “m” şeklinde birim belirten “suffixText”ler koyalım. Bir de kullanıcın sadece sayı girmesini istediğimiz için “keyboardType” parametresiyle iki “TextField” için de giriş yöntemini “number” olarak ayarlayalım.
ana_sayfa.dart
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Vücut Kitle Endeksi"), ), body: Padding( padding: EdgeInsets.symmetric(vertical: 48, horizontal: 16), child: Column( children: [ Text( _sonuc.toString(), style: TextStyle(fontSize: 48), ), SizedBox(height: 32), TextField( controller: _boyController, keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), labelText: "Boy", suffixText: "m", ), ), SizedBox(height: 16), TextField( controller: _kiloController, keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), labelText: "Kilo", suffixText: "kg", ), ), SizedBox(height: 32), RaisedButton( child: Text("Hesapla"), onPressed: _hesapla, ), ], ), ), ); }
Tasarımı tamamladık ancak bir sorunumuz var. Klavye açıldığında aşağıdaki gibi garip bir görüntüyle karşılaşıyoruz.
Uygulamada trafik işaretine benzer değişik bir işaret görüyoruz. Bu işaret Column‘un sınırlarını aştığımızı, yani Column‘un içindeki öğelerin Column‘a sığmadığını gösteriyor. Peki klavye kapalıyken öğeler sığıyor da klavye açılınca neden sığmamaya başlıyor? Çünkü, klavye açıldığında Scaffold‘un “body” kısmı küçülür ve klavyenin üstüne sığmaya çalışır. Yani Scaffold, ekrandaki tüm öğeleri (Text, TextField’lar ve RaisedButton) klavye ile “AppBar” arasındaki o küçük kısma sığdırmaya çalışıyor. Ancak biz bunu istemiyoruz. widget’larımız yerli yerinde dursun, klavye onların üzerinde açılsın istiyoruz. Bunu yapmak için, Scaffold‘un “resizeToAvoidBottomPadding” parametresine “false” değerini atamamız gerekiyor.
ana_sayfa.dart
return Scaffold( resizeToAvoidBottomPadding: false, appBar: AppBar(
Böylece sorunumuz çözüldü ve klavyeyi açtığımızda artık istediğimiz gibi aşağıdaki görüntüyü elde ediyoruz, yani klavye artık widget’ların üzerine açılıyor.
Böylece tasarım kısmını tamamlamış olduk. Mükemmel bir görsele ulaştığımız söylenemez belki ama en azından görseli nasıl düzenleyebileceğimizle ilgili bilgilerimizi pekiştirmiş olduk. Gerçek bir uygulama yaparken tasarımcı bize tasarlayacağımız görseli verir. Bizim bilmemiz gereken, bu tasarımı Flutter’da nasıl hayata geçireceğimizdir.
Şimdi “_hesapla” fonksiyonunun içini dolduralım. Yapmamız gereken, kullanıcıdan aldığımız değerlere göre vücut kitle endeksi hesaplamasını yapmak ve bunu setState ile “_sonuc” değişkenine atamak. İlk adımımız ise “AnaSayfa”yı StatefulWidget‘a çevirmek olacaktır (Bunu neden yapmamız gerektiğini ve nasıl yapacağımızı başta “Stateful Widget” başlığı altında olmak üzere birçok kez konuştuk, o yüzden ayrıntıya girmeyeceğim). Ardından fonksiyonumuzun gövdesini doldurabiliriz.
ana_sayfa.dart
_hesapla() { String boyText = _boyController.text.trim(); String kiloText = _kiloController.text.trim(); double boy = double.parse(boyText); double kilo = double.parse(kiloText); setState(() { _sonuc = kilo / (boy * boy); }); }
Fonksiyonu adım adım inceleyelim. ilk önce “controller” aracılığıyla iki TextField’daki değeri de okuyup birer String değişkene atıyoruz. Kullanıcının TextField’a girdiği değer sayı da olsa her zaman String türünde gelir. Satırların sonundaki “trim()” fonksiyonu ise String’in başında veya sonunda boşluk (space) varsa o boşlukları temizler. Ardından bu String değerleri double’a çeviriyoruz. Sonra da gerekli hesaplamayı yapıp sonucunu setState içinde “_sonuc” değişkenine atıyoruz. İşlem bu kadar, ancak ekranda istemediğimiz bir görüntü var.
Virgülden sonra o kadar çok hane gösteriliyor ki sayı tek satıra sığmıyor. Biz “_sonuc” değerinin virgülden sonraki sadece iki rakamını göstermek istiyoruz. Bunun için Text widget’ı içindeki “_sonuc.toString()” değerini “_sonuc.toStringAsFixed(2)” ile değiştireceğiz.
ana_sayfa.dart
Text( _sonuc.toStringAsFixed(2), style: TextStyle(fontSize: 48), ),
Böylece sorunumuz çözülmüş oldu. Son olarak, eğer kullanıcı double’a çevrilemeyecek bir değer girerse ne olacak ona bakalım. Klavyede sadece numara gösteriyoruz ama numaraların yanı sıra klavyede tire (-) gibi işaretler de var. Bu işaret kullanıcının negatif sayılar girebilmesi için klavyede bulunuyor ama örneğin kullanıcının “1.-85” gibi geçersiz bir değer girdiğini varsayalım. Bu durumda konsolda aşağıdaki gibi bir hata alırız.
Hata doğal olarak bize “1.-85” ifadesinin double’a dönüştürülemeyeceğini söylüyor. Bu durum gerçek hayatta uygulamamızın çökmesine neden olabilir. O yüzden alınan değeri double’a çevirme işleminden sonrasını “try – catch” içinde yapmamız daha doğru olacaktır.
ana_sayfa.dart
_hesapla() { String boyText = _boyController.text.trim(); String kiloText = _kiloController.text.trim(); try { double boy = double.parse(boyText); double kilo = double.parse(kiloText); setState(() { _sonuc = kilo / (boy * boy); }); } catch (error) { print("Hatalı işlem yapıldı. Hata mesajı: ${error.toString()}"); } }
Böylece kodumuz daha güvenli bir hâl aldı.
Kodun son halini aşağıda görebilirsiniz.
ana_sayfa.dart
import 'package:flutter/material.dart'; class AnaSayfa extends StatefulWidget { @override _AnaSayfaState createState() => _AnaSayfaState(); } class _AnaSayfaState extends State<AnaSayfa> { double _sonuc = 0; TextEditingController _boyController = TextEditingController(); TextEditingController _kiloController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomPadding: false, appBar: AppBar( title: Text("Vücut Kitle Endeksi"), ), body: Padding( padding: EdgeInsets.symmetric(vertical: 48, horizontal: 16), child: Column( children: [ Text( _sonuc.toStringAsFixed(2), style: TextStyle(fontSize: 48), ), SizedBox(height: 32), TextField( controller: _boyController, keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), labelText: "Boy", suffixText: "m", ), ), SizedBox(height: 16), TextField( controller: _kiloController, keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), labelText: "Kilo", suffixText: "kg", ), ), SizedBox(height: 32), RaisedButton( child: Text("Hesapla"), onPressed: _hesapla, ), ], ), ), ); } _hesapla() { String boyText = _boyController.text.trim(); String kiloText = _kiloController.text.trim(); try { double boy = double.parse(boyText); double kilo = double.parse(kiloText); setState(() { _sonuc = kilo / (boy * boy); }); } catch (error) { print("Hatalı işlem yapıldı. Hata mesajı: ${error.toString()}"); } } }
Böylece ilk örnek uygulamamız olan “Vücut Kitle Endeksi” uygulamasını başarıyla tamamlamış olduk.