Ekran tasarımı karmaşıklaştıkça “_build” fonksiyonu da uzayacaktır. Böyle olması durumunda da bir yerde değişiklik yapacağımız zaman o kısmı bulmakta zorlanabiliriz. Bunun önüne geçmek için en iyi yöntem, “_build” fonksiyonunu alt fonksiyonlara parçalamaktır. Örneğin, aşağıdaki gibi bir kod düşünelim.
ana_sayfa.dart
import 'package:flutter/material.dart'; class AnaSayfa extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Ana Sayfa"), ), body: Center( child: Text( "Merhaba Dünya", style: TextStyle(fontSize: 36), ), ), ); } }
Yukarıdaki kod parçasında gösterilen sayfa, içinde “Ana Sayfa” yazan bir AppBar ve ‘body’ olarak da Center widget’ıyla sarmalanmış, içinde “Merhaba Dünya” yazan bir Text içeriyor. Bu tanımlamaları doğrudan “_build” içinde yapmak yerine, Widget türünde değer döndüren “_buildAppBar” ve “_buildBody” isminde fonksiyonlar tanımlayıp döndürdükleri bu değerleri Scaffold‘un appBar ve body parametrelerine atayabiliriz.
ana_sayfa.dart
import 'package:flutter/material.dart'; class AnaSayfa extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: _buildAppBar(), body: _buildBody(), ); } Widget _buildAppBar() { return AppBar( title: Text("Ana Sayfa"), ); } Widget _buildBody() { return Center( child: Text( "Merhaba Dünya", style: TextStyle(fontSize: 36), ), ); } }
Böylece parçalama işlemimizi tamamlamış olduk. Artık her seferinde “build” fonksiyonuna gitmek yerine, AppBar‘da değişiklik yapacaksak “_buildAppBar” fonksiyonuna, ‘body’de değişiklik yapacaksak “_buildBody” fonksiyonuna bakmamız yeterli olacaktır. Böylece aradığımız kısmı daha rahat bulabileceğiz.
“İnternetten Veri Çekme” konusunu öğrenirken hayata geçirdiğimiz “kur_donusturucu” projesinde, “AnaSayfa”daki “build” fonksiyonu oldukça uzun olmuştu. O kodu ve ekran görüntüsünü bir hatırlayalım.
ana_sayfa.dart
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class AnaSayfa extends StatefulWidget { @override _AnaSayfaState createState() => _AnaSayfaState(); } class _AnaSayfaState extends State<AnaSayfa> { Map<String, double> _oranlar = {}; String _secilenKur = "USD"; double _sonuc = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _verileriInternettenCek(); }); } @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomPadding: false, appBar: AppBar( title: Text("Kur Dönüştürücü"), ), body: Padding( padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), child: Column( children: [ Row( children: [ Expanded( child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), onChanged: _hesapla, ), ), SizedBox(width: 16), DropdownButton<String>( value: _secilenKur, icon: Icon(Icons.arrow_downward), underline: Container(), onChanged: (String newValue) { setState(() { _secilenKur = newValue; }); }, items: _oranlar.keys .map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ), ], ), SizedBox(height: 16), Text( "${_sonuc.toStringAsFixed(2)} TL", style: TextStyle(fontSize: 24), ), SizedBox(height: 16), Container( height: 2, color: Colors.black, ), Expanded( child: ListView.builder( itemCount: _oranlar.keys.length, itemBuilder: _buildListItem, ), ), ], ), ), ); } Widget _buildListItem(BuildContext context, int index) { return ListTile( title: Text(_oranlar.keys.toList()[index]), trailing: Text( (1 / _oranlar.values.toList()[index]).toStringAsFixed(2) + " TL"), ); } _hesapla(String girilenDeger) { try { double deger = double.parse(girilenDeger); setState(() { _sonuc = deger / _oranlar[_secilenKur]; }); } catch (e) { print("Bir hata oluştu"); } } _verileriInternettenCek() async { http.Response response = await http.get("https://api.exchangeratesapi.io/latest?base=TRY"); Map<String, dynamic> parsedResponse = jsonDecode(response.body); Map<String, dynamic> rates = parsedResponse["rates"]; for (String ulkeKuru in rates.keys) { _oranlar[ulkeKuru] = rates[ulkeKuru] as double; } setState(() {}); } }
İlk olarak “appBar” ve “body” parametreleri için, daha önce yaptığımız gibi ayrı birer fonksiyon oluşturalım ve “Column”a kadar olan kısmı “_buildBody” içine yerleştirip şimdilik “Column”un içini doldurmayalım.
ana_sayfa.dart
Widget _buildAppBar() { return AppBar( title: Text("Kur Dönüştürücü"), ); } Widget _buildBody() { return Padding( padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), child: Column( children: [ ], ), ); }
“body” fazla uzun olduğu için, “_buildBody” fonksiyonunu da kendi içinde parçalayacağız. İlk olarak, “_buildRow” isminde bir fonksiyon oluşturalım. Bu fonksiyonu da “_buildTextField” ve “_buildDropdown” olmak üzere iki alt fonksiyona bölelim.
ana_sayfa.dart
Widget _buildRow() { return Row( children: [ _buildTextField(), SizedBox(width: 16), _buildDropdown(), ], ); } Widget _buildTextField() { return Expanded( child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), onChanged: _hesapla, ), ); } Widget _buildDropdown() { return DropdownButton<String>( value: _secilenKur, icon: Icon(Icons.arrow_downward), underline: Container(), onChanged: (String newValue) { setState(() { _secilenKur = newValue; }); }, items: _oranlar.keys.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ); }
“_buildRow” içinde “_buildTextField” ve “_buildDropdown” fonksiyonlarını çağırdık ve aralarına SizedBox ile boşluk koyduk.
Son olarak, Column içindeki sonuç Text‘ini, ortadaki çizgiyi temsil eden Container‘ı ve en altta yer alan ListView‘ı da ayrı fonksiyonlarda oluşturalım.
ana_sayfa.dart
Widget _buildSonucText() { return Text( "${_sonuc.toStringAsFixed(2)} TL", style: TextStyle(fontSize: 24), ); } Widget _buildAyiriciCizgi() { return Container( height: 2, color: Colors.black, ); } Widget _buildListView() { return Expanded( child: ListView.builder( itemCount: _oranlar.keys.length, itemBuilder: _buildListItem, ), ); }
Fonksiyonların isimleri yarı İngilizce yarı Türkçe oldu ama işlevleri anlaşılsın diye bu isimleri verdim. Siz daha güzel isimlendirme yapabilirsiniz. Son fonksiyonları da oluşturduğumuza göre artık tek yapmamız gereken, “buildBody” fonksiyonundaki Column içinde bu alt fonksiyonları çağırmak olacaktır.
ana_sayfa.dart
Widget _buildBody() { return Padding( padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), child: Column( children: [ _buildRow(), SizedBox(height: 16), _buildSonucText(), SizedBox(height: 16), _buildAyiriciCizgi(), _buildListView(), ], ), ); }
3.15.1. CircularProgressIndicator
Son bir düzenleme daha yapalım. İnternetten veri çeken uygulamaların hemen hemen hiçbirinde veri çekilene kadar boş bir ekran görmeyiz. Onun yerine, verinin yüklenmekte olduğunu gösteren bir sembol, resim veya yazı görürüz. Biz bu örneğimizde eğer veri henüz yüklenmemişse aşağıdaki resimde olduğu gibi ekranın ortasında dönen bir çember göstereceğiz ve bu kullanıcıya verilerin henüz yüklenmediğini, yüklenmekte olduğunu gösterecek.
Bunun için tek yapmamız gereken, “build” fonksiyonunu aşağıdaki gibi düzenlemektir.
ana_sayfa.dart
@override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomPadding: false, appBar: _buildAppBar(), body: _oranlar.length > 0 ? _buildBody() : Center(child: CircularProgressIndicator()), ); }
‘body’ parametresine dikkat edelim. Bu yapıyı Dart öğrenirken If yapısını incelediğimiz başlık altında Ternary – If alt başlığıyla görmüştük. Kısaca özetlemek gerekirse ilk olarak bir “koşul” görüyoruz. Bu koşul “_oranlar” isimli Map’imizin eleman sayısının sıfırdan büyük olup olmadığını sorguluyor. Eğer eleman sayısı sıfırdan büyükse o halde veriler internetten çekilmiş demektir ve projemize ait ‘body’yi inşa edebiliriz. Eğer “_oranlar” Map’inin eleman sayısı sıfırsa, yani henüz veri çekilmemişse ekranın tam ortasında, yani Center widget’ının içinde bir CircularProgressIndicator inşa ediyoruz. Böylece yukarıdaki ekran görüntüsünü elde etmiş oluyoruz.
Uygulama ilk açıldığında henüz veri çekilmediği için ekranda CircularProgressIndicator göreceğiz. Ardından, veri çekme işlemi tamamlanacak ve “_verileriInternettenCek” fonksiyonunun sonunda setState çalıştığı için “build” fonksiyonu çağrılacak, böylece ekran yeniden çizilecektir. Bu defa “_oranlar” isimli Map’in içi veriyle dolu olduğu için “_oranlar.length > 0” koşulu doğru olacak, bu yüzden “_buildBody” çalışacak ve ‘body’miz inşa edilecektir. Böylece farklı koşullarda ekrana farklı widget çizdirmeyi de görmüş olduk.
“AnaSayfa” sınıfının son hali aşağıdaki gibidir.
ana_sayfa.dart
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class AnaSayfa extends StatefulWidget { @override _AnaSayfaState createState() => _AnaSayfaState(); } class _AnaSayfaState extends State<AnaSayfa> { Map<String, double> _oranlar = {}; String _secilenKur = "USD"; double _sonuc = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _verileriInternettenCek(); }); } @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomPadding: false, appBar: _buildAppBar(), body: _oranlar.length > 0 ? _buildBody() : Center(child: CircularProgressIndicator()), ); } Widget _buildAppBar() { return AppBar( title: Text("Kur Dönüştürücü"), ); } Widget _buildBody() { return Padding( padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), child: Column( children: [ _buildRow(), SizedBox(height: 16), _buildSonucText(), SizedBox(height: 16), _buildAyiriciCizgi(), _buildListView(), ], ), ); } Widget _buildRow() { return Row( children: [ _buildTextField(), SizedBox(width: 16), _buildDropdown(), ], ); } Widget _buildTextField() { return Expanded( child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), onChanged: _hesapla, ), ); } Widget _buildDropdown() { return DropdownButton<String>( value: _secilenKur, icon: Icon(Icons.arrow_downward), underline: Container(), onChanged: (String newValue) { setState(() { _secilenKur = newValue; }); }, items: _oranlar.keys.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ); } Widget _buildSonucText() { return Text( "${_sonuc.toStringAsFixed(2)} TL", style: TextStyle(fontSize: 24), ); } Widget _buildAyiriciCizgi() { return Container( height: 2, color: Colors.black, ); } Widget _buildListView() { return Expanded( child: ListView.builder( itemCount: _oranlar.keys.length, itemBuilder: _buildListItem, ), ); } Widget _buildListItem(BuildContext context, int index) { return ListTile( title: Text(_oranlar.keys.toList()[index]), trailing: Text( (1 / _oranlar.values.toList()[index]).toStringAsFixed(2) + " TL"), ); } _hesapla(String girilenDeger) { try { double deger = double.parse(girilenDeger); setState(() { _sonuc = deger / _oranlar[_secilenKur]; }); } catch (e) { print("Bir hata oluştu"); } } _verileriInternettenCek() async { http.Response response = await http.get("https://api.exchangeratesapi.io/latest?base=TRY"); Map<String, dynamic> parsedResponse = jsonDecode(response.body); Map<String, dynamic> rates = parsedResponse["rates"]; for (String ulkeKuru in rates.keys) { _oranlar[ulkeKuru] = rates[ulkeKuru] as double; } setState(() {}); } }
Aynı işlevi elde etmekle birlikte, belli görevler için alt fonksiyonlara ayrılmış, daha derli toplu bir kod yazmış olduk. Ayrıca veriler yüklenene kadar kullanıcıya verilerin çekilmekte olduğunu gösteren bir de görsel ekledik.