13 Ağustos 2018 Pazartesi

Android Fragment Caching

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. 

FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
Fragment frStack = manager.findFragmentByTag("Tag");


if (frStack == null) {
    transaction.replace(R.id.main_fl, frNew, "Tag");
} else {
    transaction.replace(R.id.main_fl, frStack);
}
transaction.addToBackStack(null);
transaction.commit();


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.

private View view;
private boolean isLoaded;

@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup cntr,Bundle savedInstanceState) {

    if (isLoaded == false) {
        view = inflater.inflate(R.layout.fragment_scroll, cntr);
...
        service call, findbyViewIds ...
...
        isLoaded = true;
    }

    return view;
}

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. 

29 Haziran 2018 Cuma

Android Kod Optimizasyonu 

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.
bkz: LİNT

YARARI NEDİR

“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 ;
  • Kodunuzu potansiyel oluşabilecek buglardan arındıracaksınız.
  • Apk boyutunuz azalacak.
  • Kodunuz daha temiz olacak.

NASIL KULLANACAĞIZ

YÖNTEM 1
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..
Declaration redundancy > Unused declaration = Kullanılmayan kodlar. parametre, sınıf, method…
Ö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ı,
<?xml version="1.0" encoding="UTF-8"?><lint>
    <!-- kullanılmayan kaynaklarda hata fırlat -->
    <issue id="UnusedResources" severity="error"/>
    <!-- hardcodedtext lerde hata fırlat -->
    <issue id="HardcodedText" severity="error" />
    
</lint>
Ardından build.gradle da bu yazdığınız xml dosyasının yolunu belirtmelisiniz.
android {
    ...
    lintOptions {
        lintConfig file("src/main/res/xml/lint.xml")
    }
}
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.

20 Mart 2018 Salı

Temel Rxjava (4)

zip()
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.
Observable.zip(

Observable.just("foto güncelleme 3 saniye")
.delay(3, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread()),

Observable.just("diger servis 6 saniyelik gecikme")
.delay(6, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread()),

(o1, o2) -> new Object())
        
.doOnSubscribe(disposable -> 
startTime = System.currentTimeMillis())
        
.doOnComplete(() -> {
            
long totalDuration = System.currentTimeMillis() - startTime;
Log.d("Result","Total : " + totalDuration + "ms");
        })
.subscribe();
D/Result: Total : 6003ms
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(new Action1<String>() {
            @Override
            public void call(String value) {
               // 500 ms sonra tetiklenecek değer
            }
        });