Uygulamanızda birden çok fragment olduğunu ve aynı fragmentları tekrar tekrar kullandığınızı varsayın. Yapmak istediğimiz ise kullanılan fragmentları cachelemek ve tekrar oluşturma maaliyetinden kurtulmak, bellek yönetimiyle uğraşmamak. Bunun Fragment API da bulunan için hali hazırda bir sınıf var. FragmentManager.
NEDEN
Örnek olarak instagram uygulamasında 5 tane tab var ve her sayfa kendi içinde scroll edildiğinde aynı data ve aynı pozisyonda kaldıkları yerden devam edebiliyor. Bizim yapmak istediğimizde bu şekilde hem data hem de UI hiç bozulmadan kaldığı yerden devam edebilecek bir yapı inşa etmek.
NASIL
1.Adım
add methodunu kullanarak sürekli yeni fragment oluşturmuş oluruz. Yani ne datayı ne de UI saklamış oluruz. Yani yapmamız gereken replace i kullanmak.
Yukarıda yapmak istediğimiz yeni oluşturulacak olan fragment stackte var mı(findFragmentByTag). Eğer yoksa yeni fragmentı ekle varsa eski mevcutta oluşturulmuş olan fragmentı.
2.Adım
Ama replace ile çağırdığımızda da göreceksiniz ki oncreateview onresume … methodlarına tekrar girecek (bakınız fragment lyfecycle ). Yapmamız gereken ne oncreateview() veya onresume methodunda çağırdığınız fonksiyonları tekrardan çağırmadan sadece rootview i return etmek.
2.adımı farklı yöntemlerle de yapabilirsiniz! Önemli olan sayfa ilk açıldığında yapılması gereken işlemlerin -servis çağrısı, setonclick işlemleri gibi- replace de yapılmaması.
SONUÇ
Aşağıdaki ekran görüntüsünden tüm fragmentların durumlarını koruduğunu görebilirisniz. Örnek proje linkte mevcut.
DİPNOT1
Burada show()-hide() yolunu da izleyebilir, caching neden gerekli diyebilirsiniz. Ama bu yöntem tüm fragmentlarınızı ayakta tutar yani hepsi aslında onresume da kalır. Replace ettiğiniz fragmentler ise sırasıyla pause stop… methodlarından geçer yani container dan tamamen o fragmentı sökmüş olursunuz. Replace etmek “hide ardından show etme” işlemi göre daha maaliyetli olmasına karşın memory den kazandırma gibi önemli bir kozu var. Hide ettikten sonra ayakta kalan tüm fragmentlarınızın heap te kaldığını ve outofmemory hatası alabileceğinizi göz önünde bulundurmalısınız.
DİPNOT2
transaction.addToBackStack(null);
addtobackstack methodu yaptığımız replace işleminin stackte bir karşılığı olduğunu belirtiyor. Aksi halde tablar arası geçişte cachte ten hiçbirşey gelmediğini ve geri butonunda tüm fragmentlarınızın tek seferde kaybolduğunu göreceksiniz.
Optimizasyon adından da anlaşıldığı üzere mevcutta var olan, çalışan bir şeyi daha iyi hale getirmektir. Kod optimizasyonuna ise kullanılmayan kaynakların (unused strings,image, layout,code) silinmesiyle başlayabilirsiniz. ve bunu yapan android studio’ya entegre bir tool zaten mevcut.
“Ne optimizasyonu şimdi, zaten süre sıkışık, yetişmesi gereken onca işin arasında kim uğraşacak ”diyebilirsiniz. Normal. Dedik biz de yalan yok. Ama şöyle düşünün ki ;
Tool u kullanmak için yapmanız gereken menü bardaki Analyze > Inspect Code a tıklamak. Açılan pencere aşağıdaki gibi olacaktır. Burada proje geneli veya özellikle bir modül vs. tarama yapılsın diye seçenekler mevcut.
Tamam a tıkladığınızda tarama yapılır ve Inspection Results alanında uygulamanıdaki kod iyileştirmeleri gereken veya bug çıkarabilecek kısımlar size gösterir.
Projeniz biraz genişlediyse ekranınızda yukarıdaki resime oranla 2–3 kat fazla sonuç çıkmış olabilir. Bazen çok enteresan hatalar da. Ama en temel uygulamadaki gereksiz kaynakların ve kodların silinmesini istiyorsanız aşağıdaki yolu izleyebilirsiniz.
Android Lint:Performance > Unused resource = Kullanılmayan kaynaklar. image, strings, layout, layout içindeki başıboş id ler, style.xml ler vs..
Özellikle unused resource alanı tamamen gereksiz şeyleri barındırıyor burada bulunanları silebilirsiniz. Ama unused decleration kısmındakileri silerken tek tek incelemenizde fayda var.”Ben annotation ların tetiklediği methodları silmeye çalıştığına bizzat şahit oldum. “ Örnek: Otto ”
YÖNTEM 2
Uygulamayı bu şekilde sonradan taramak yerine her build ettiğinizde bu hataları görme gibi de bir seçeneğiniz var. Bunun için res klasörünün altına bir xml klasörü açıp onun içine de aşağıdaki örnekte olduğu gibi bir xml dosyası tanımlamasınız.
Dosya hata fırlatmasını istediğiniz durumları içermelidir. Kullanılmayan çeviriler, layout performans problemleri, manifest hataları,
Kodu derlediğinizde aşağıda bulunan örnekteki gibi bir sonuç ekranı çıkacaktır. İlk olarak size apk yı bu şekilde hatalı da olsa atabilmeniz için bir öneri sunuyor. “abortOnError false yapmazsanız hataları çözmeden apk oluşturulmaz”. Onun altında ise sıra sıra hatalar listelenir.
Dipnot: Tüm bunlara ek olarak FindBugs , PMD gibi toolları da inceleyebilirsiniz.
Bir sayfada iki tane servise çıktığınız düşünün. Örnek profil güncelleme. Bunun içinde aynı anda iki tane servise çıkıyorsunuz. Profil fotoğrafı güncellemek için ayrı, ad soyad vs için ayrı. Senaryo şu şekilde.
Güncelle butonuna basıldığı anda ekranda progress başlatılacak
İki servis çağrılacak aynı anda çağrılacak
İkisinden de cevap geldiği anda progress kaldırılacak
Şimdi burda şöyle bir sıkıntı var. Hangi servisten önce cevap dönecek. Progressi kaldırmamız için ikisinden de cevap dönmüş olması lazım. Bunun için rxjava daki reduce() methodunu kullanabilirsiniz.
Aynı anda çağırılan iki servis için de yeni thread açıldığı için aynı anda başlamıştır. doOnComplete ise ikinci servisten cevap geldiğinde çağrılmıştır. Yeni thread açmazsanız senkron olarak işletilir.
debounce()
Bir şeyler search edeceğiniz zaman genelde siz yazdıktan sonra biraz zaman geçer ”200ms–300ms” sonra tetiklenme olur.- Her harf için işlem yapılmasın da, tam olarak ne istediğini yazsın ondan sonra bir arama gerçekleşsin.-
Klasik yöntem textchangelistener içinde bir timer başlatılır. Her girilen yeni karakter için timer sıfırlanır , yeniden başlatılır. Bende bu şekilde yazıyorum hala . Ama debounce ile bu nasıl yapılır bir de ona bakalım.
RxTextView.textChanges(editTextVariableName)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe(newAction1<String>() {
@Override
public voidcall(String value) {
// 500 ms sonra tetiklenecek değer}
});
Rxjava yı geleneksel java koduna göre bir adım öne geçiren olayın , yayınlanan datayı işlemek için çok geniş metod seti olduğundan ilk yazdımda bahsetmiştim . Bu yazımda bunlardan birkaçını açıklamaya çalışacağım .
flatMap()
Bir observable ın sonucunda oluşan değer tekrar başka bir observable a bağlanıyorsa . Yani asenkron bir işlem sonucu aslında başka bir asenkron işlem sonucuna bağlıysa içiçe iki tane observable yazmaktansa flatmap kullanılır .
Örnekte ilk olarak Integer dönen bir observable oluşturulmuştur . Başka bir observable bunu dinlemektedir ve dönen sonuca göre String bir değer dönmektedir .
reduce()
Bir Observable birden çok değer üretiyor ve sonuc olarak bu değerlerin toplamı lazımsa reduce kullanılabilir .
Observable.create(new ObservableOnSubscribe>() {
@Override
public void subscribe(ObservableEmitter> e) throws Exception {
ArrayList strings = new ArrayList<>();
strings.add("Hello");
strings.add("Bro");
e.onNext(strings);
strings = new ArrayList<>();
strings.add("Whats");
strings.add("Up");
e.onNext(strings);
e.onComplete();
}
}).reduce(new BiFunction, List, List>() {
@Override
public List apply(List strings, List strings2) throws Exception {
strings.addAll(strings2);
return strings;
}
}).subscribe(new Consumer>() {
@Override
public void accept(List strings) throws Exception {
Toast.makeText(MainActivity.this, strings.toString(), Toast.LENGTH_SHORT).show();
}
});
Ekran Çıktısı : [Hello,Bro,Whatsup,Kro]
onNext ile yayınlanan her "strings" listesi reduce methoduna girer ve orada parent strings e eklenir . Subsribe ise onComplete den sonra çalışır ve sonuç olarak iki onNext metoduyla yayınlanmış olan listelerin artarda eklenmiş halini alır .
Bir önceki yazımda observable nedir nasıl kullanılırdan bahsetmiştim . Şimdi ise Observable yerine kullanılabilecek diğer dinleyici türlerine bir göz atalım .
Observable -> Birden fazla kez dinleyici tetiklenecek ise kullanılır . Bu yüzden onNext , onComplete adında iki methodu vardır . Her yayınlamada onNext tetiklenirken tüm akış bittiğinde ise onComplete tetiklenir .
Observable.create(new ObservableOnSubscribe
Single -> Dinleyici sadece bir kez tetiklecek ise kullanılır . Bu yüzden onSuccess methodu mevcuttur .
Single.create(new SingleOnSubscribe() {
@Override
public void subscribe(SingleEmitter e) throws Exception {
e.onSuccess("Hello");
}
}).subscribe(new SingleObserver() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onSuccess(Object o) {
Log.d(Tag, o);
}
@Override
public void onError(Throwable e) {}
});
Completable - > Single ile aynı gibi görünsede burada dinleyici tetiklenirken observable dan herhangi bir değer beklenmez . Arkaplanda bir işlem gerçekleştirilir ama sadece bitip bitmediğiyle ilgilenilir .
Completable.create(new CompletableOnSubscribe() {
@Override
public void subscribe(CompletableEmitter e) throws Exception {
e.onComplete();
}
}).subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onComplete() {
Log.d(Tag, "done");
}
@Override
public void onError(Throwable e) {}
});
Maybe -> Single ile Completable birleşimi gibidir . Hem onSuccess hem de onComplete methodları mevcut . Yani hiçbir sonuç dönmeyedebilir , tek bir sonuç dönedebilir .
Maybe.create(new MaybeOnSubscribe() {
@Override
public void subscribe(MaybeEmitter e) throws Exception {
e.onSuccess("Hello");
// e.onComplete();
}
}).subscribe(new MaybeObserver() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onSuccess(String s) {
Log.d(Tag, s);
}
@Override
public void onError(Throwable e) {}
@Override
public void onComplete() {
Log.d(Tag, "done");
}
});
RxJava (Reactive Extension) en temelde observable pattern üzerine kurulu asenkron işlemler için kullanılan güçlü bir kütüphanedir . Kendi içinde 3 parçaya ayrılır .
Observable = Datanın yayınlandığı yer Observer = Datanın dinlendiği yer Datayı İşleme = Data üzerinde filtreleme , ekleme , çıkarma vs.. işlemlerin yapıldığı yer
Yukarıdaki örnekte en basit haliyle bir örnek görebilirisiniz . "Hello World" yaylınlanıyor -> trim() ile filtreleniyor -> subscribe edilen yerde sonuç görüntüleniyor .
Neden RxJava Kullanmalıyım ?
Yukarıdaki örnekte bir string yayınlanmış onu da bir yerden dinlemiş basmışsın , bunun için mi yani bu rxjava diyebilirsiniz :) haklısınız . Peki ama neden neden kullanmalıyım diye sorarsanız !
(Java)
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
final List strings = new ArrayList<>();
strings.add("Hello");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, strings.toString(), Toast.LENGTH_SHORT).show();
}
});
}
}).start();
Yapmak istediğim çok basit , bana bir liste dönecek olan bir işlem ama 500 milisaniyelik bir gecikme ile cevap veriyor . Cihazda UI kitlemeden arkaplanda bir işlem yapacağınızı düşünün ki bunu yapmak için yeni bir thread açmanız gerekiyor -Örnek amaçlı ben sonucu liste dönen bir işleme Thread.sleep(500) ile bir gecikme ekledim -. Ardından sonucu ekrana basacığınızı düşünün ki bunun içinde tekrar UI thread e geçmeniz yani bir runOnUiThread başlatmanız gerekiyor -Aksi takdirde exception alırsınız -.
Bunun yerine
(RxJava)
Observable.fromCallable(new Callable>() {
@Override
public List call() throws Exception {
Thread.sleep(500);
List strings = new ArrayList<>();
strings.add("Hello");
return strings;
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer>() {
@Override
public void accept(List strings) throws Exception {
Toast.makeText(MainActivity.this, strings.toString(), Toast.LENGTH_SHORT).show();
}
});
ObserveOn = Subsriber hangi threadde çalışması dinlemesi gerektiğini belirtiyoruz . -AndroidSchedulers.mainThread() yani main threadde dinle . - SubscribeOn = Observable sonucu yayınlanacak olacak işlemin hangi threadde çalışması gerektiğini belirtiyoruz . -Ben newThread() kullandım ama Schedulers.io da aynı işi yapıyor sadece var olan ama boşa çıkmış threadlerden birini kullanıyor .-
Bunlara ek olarak RxJava ile data üzerinde işlem yapabileceğiniz çok güçlü bir method set (Operators) olduğunu hesaba katarsak . RxJava geleneksel java koduna göre daha baskın geliyor .
Android uygulama geliştirirken UI ile java sınıflarını birbirine bağlamanın en sıkıcı işlerden biri olduğu bilinir . Bu sebeptendir ki bu amaçla kullanılan Butterknife şu android geliştiricileri arasında en popüler kütüphanlerden biri haline geldi . Son zamanlarda sıkıntıyı farketmiş olucaklar ki Google/IO 2015 te yeni data binding support library i tanıttı . Bu kütüphane sayesinde itemlerinizi tek tek arayüzden findByViewId ile yakalayıp ardından onları cast edip -hataya gayet açık yöntemler- ardından onlara tek tekmodelinizdeki ilgili parametreleri doldurmak gibi zahmetli bir işten kurtulabilirsiniz . Kullanımı gayet basit tutulan kütüphanenin örneğini sayfanın en altındaki linkten bulabilirsiniz .
STEP 1 (Temel)
İlk olarak kütüphaneyi kullanabilmeniz için build.gradle dosyanızdan dataBinding i aktif hale getirmeniz yeterli . "Gradle version minimum 1.5.0 olmalı !"
Kullancağınız layout dosyalarında ise dikkat etmeniz gereken ilk şey "<layout>" tag' ile başlayıp derleyiciye bu dosyayı bind etmesi gerektiğini belirtmektir.
Tebrikler artık findByViewId methodundan kurtuldunuz . Bu dosyadaki itemlerinizi direk activity sınıfınızdan kullanabilirsiniz .
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main);
activityMainBinding.text.setText("Hello World Test"); }
STEP 2(Arayüze Müdahale)
Android binding library yukarıda da görüldüğü gibi ilk etapta findByViewId ile araryüzü activity e bağlama zorunluluğunu ortadan kaldırmıştır . Tabi sadece bununla sınırlı olmayan kütüphane ile modellerinizi de bu arayüz itemlerine zahmetsiz init edebilirsiniz .
Bunun için ilk yapmanız gereken layout dosyanızda kullancağınız model i "<data>" tag i içerinde belirtmek .
Örnekte kullanılacak olan TestModel i sadece textStr adında bir string içeren basit bir pojo modeli olarak düşünebilirsiniz . TextView altında bulunan android:text="@{testModel.textStr}" tagi bu textview a modeldeki textStr nin doldurulacağı anlamına gelir . Artık tek yapmanız gereken activity sınıfınızdan arayüze modelinizi setlemek .
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main);
Test test = new Test("Hello World Test 2");
activityMainBinding.setTestModel(test);
}
Kullanımı gayet basit tutulan kütüphanenin örneğini linkten bulabilirsiniz . -Picasso ile image nasıl eklenir , layout a logic nasıl yazılır linkte mevcut -