Android Kotlin Hakkında Temel Bilgiler 05.1: ViewModel ve ViewModelFactory

Bu codelab, Android Kotlin Hakkında Temel Bilgiler kursunun bir parçasıdır. Bu kurstan en iyi şekilde yararlanmak için codelab'leri sırayla tamamlamanızı öneririz. Kursla ilgili tüm codelab'ler Android Kotlin Hakkında Temel Bilgiler codelab'leri açılış sayfasında listelenir.

Başlık ekranı

Oyun ekranı

Puan ekranı

Giriş

Bu codelab'de, Android Mimari Bileşenlerinden biri olan ViewModel hakkında bilgi edineceksiniz:

  • Kullanıcı arayüzüyle ilgili verileri yaşam döngüsüne duyarlı bir şekilde depolamak ve yönetmek için ViewModel sınıfını kullanırsınız. ViewModel sınıfı, ekran döndürme ve klavye kullanılabilirliğinde değişiklik gibi cihaz yapılandırması değişikliklerinde verilerin korunmasını sağlar.
  • Yapılandırma değişikliklerinden etkilenmeyen ViewModel nesnesini oluşturup döndürmek için ViewModelFactory sınıfını kullanırsınız.

Bilmeniz gerekenler

  • Kotlin'de temel Android uygulamaları oluşturma
  • Uygulamanızda gezinmeyi uygulamak için gezinme grafiğini kullanma
  • Uygulamanızın hedefleri arasında gezinmek ve gezinme hedefleri arasında veri aktarmak için kod ekleme
  • Etkinlik ve parça yaşam döngülerinin işleyiş şekli.
  • Bir uygulamaya günlük kaydı bilgilerini ekleme ve Android Studio'da Logcat'i kullanarak günlükleri okuma

Neler öğreneceksiniz?

  • Önerilen Android uygulama mimarisini kullanma
  • Uygulamanızda Lifecycle, ViewModel ve ViewModelFactory sınıflarını kullanma
  • Cihaz yapılandırması değişiklikleri sırasında kullanıcı arayüzü verilerini koruma
  • Fabrika yöntemi tasarım kalıbının ne olduğu ve nasıl kullanılacağı.
  • Arayüzü kullanarak ViewModel nesnesi oluşturma ViewModelProvider.Factory

Yapacaklarınız

  • Uygulamanın verilerini kaydetmek için uygulamaya ViewModel ekleyin. Böylece veriler, yapılandırma değişikliklerinden etkilenmez.
  • Oluşturucu parametreleriyle bir ViewModel nesnesi oluşturmak için ViewModelFactory ve fabrika yöntemi tasarım kalıbını kullanın.

5. dersteki codelab'lerde, başlangıç koduyla başlayarak GuessTheWord uygulamasını geliştirirsiniz. GuessTheWord, iki oyuncunun mümkün olan en yüksek puanı elde etmek için işbirliği yaptığı, sessiz sinema tarzı bir oyundur.

Birinci oyuncu, uygulamadaki kelimelere bakar ve her birini sırayla canlandırır. Kelimeyi ikinci oyuncuya göstermemeye dikkat eder. İkinci oyuncu kelimeyi tahmin etmeye çalışır.

Oyunu oynamak için ilk oyuncu cihazda uygulamayı açar ve aşağıdaki ekran görüntüsünde gösterildiği gibi bir kelime (ör. "gitar") görür.

İlk oyuncu, kelimeyi söylememeye dikkat ederek kelimeyi canlandırır.

  • İkinci oyuncu kelimeyi doğru tahmin ettiğinde birinci oyuncu Bildim düğmesine basar. Bu işlem, sayıyı bir artırır ve sonraki kelimeyi gösterir.
  • İkinci oyuncu kelimeyi tahmin edemezse birinci oyuncu Atla düğmesine basar. Bu durumda sayı bir azalır ve bir sonraki kelimeye geçilir.
  • Oyunu sonlandırmak için End Game (Oyunu Sonlandır) düğmesine basın. (Bu işlev, serideki ilk codelab'in başlangıç kodunda yer almaz.)

Bu görevde, başlangıç uygulamasını indirip çalıştıracak ve kodu inceleyeceksiniz.

1. adım: Başlayın

  1. GuessTheWord başlangıç kodunu indirin ve projeyi Android Studio'da açın.
  2. Uygulamayı Android destekli bir cihazda veya emülatörde çalıştırın.
  3. Düğmelere dokunun. Atla düğmesinin sonraki kelimeyi gösterdiğini ve puanı bir azalttığını, Anladım düğmesinin ise sonraki kelimeyi gösterdiğini ve puanı bir artırdığını unutmayın. Oyunu Bitir düğmesi uygulanmadığından bu düğmeye dokunduğunuzda herhangi bir işlem yapılmaz.

2. adım: Kod incelemesi yapın

  1. Uygulamanın nasıl çalıştığını anlamak için Android Studio'da kodu inceleyin.
  2. Özellikle önemli olan aşağıdaki dosyalara göz atmayı unutmayın.

MainActivity.kt

Bu dosya yalnızca varsayılan, şablonla oluşturulmuş kodu içeriyor.

res/layout/main_activity.xml

Bu dosya, uygulamanın ana düzenini içerir. Kullanıcı uygulamada gezinirken NavHostFragment diğer parçaları barındırır.

Kullanıcı arayüzü parçaları

Başlangıç kodunda, com.example.android.guesstheword.screens paketi altında üç farklı pakette üç parça bulunur:

  • Başlık ekranı için title/TitleFragment
  • Oyun ekranı için game/GameFragment
  • Skor ekranı için score/ScoreFragment

screens/title/TitleFragment.kt

Başlık parçası, uygulama başlatıldığında görüntülenen ilk ekrandır. Oyun ekranına gitmek için Oyna düğmesine bir tıklama işleyicisi ayarlanır.

screens/game/GameFragment.kt

Bu, oyunun büyük bir bölümünün geçtiği ana parçadır:

  • Değişkenler, mevcut kelime ve mevcut puan için tanımlanır.
  • resetList() yöntemi içinde tanımlanan wordList, oyunda kullanılacak kelimelerin örnek listesidir.
  • onSkip() yöntemi, Atla düğmesinin tıklama işleyicisidir. Puanı 1 azaltır ve nextWord() yöntemini kullanarak sonraki kelimeyi gösterir.
  • onCorrect() yöntemi, Anladım düğmesinin tıklama işleyicisidir. Bu yöntem, onSkip() yöntemine benzer şekilde uygulanır. Tek fark, bu yöntemin puanı azaltmak yerine 1 puan artırmasıdır.

screens/score/ScoreFragment.kt

ScoreFragment, oyundaki son ekrandır ve oyuncunun nihai puanını gösterir. Bu codelab'de, bu ekranı görüntülemek ve nihai puanı göstermek için uygulama ekleyeceksiniz.

res/navigation/main_navigation.xml

Gezinme grafiği, parçaların gezinme yoluyla nasıl bağlandığını gösterir:

  • Kullanıcı, başlık parçasından oyun parçasına gidebilir.
  • Kullanıcı, oyun fragmentinden skor fragmentine gidebilir.
  • Kullanıcı, skor fragmentinden oyun fragmentine geri dönebilir.

Bu görevde, GuessTheWord başlangıç uygulamasındaki sorunları bulursunuz.

  1. Başlangıç kodunu çalıştırın ve oyunu birkaç kelime boyunca oynayın. Her kelimeden sonra Atla veya Anladım'a dokunun.
  2. Oyun ekranında artık bir kelime ve mevcut skor gösteriliyor. Cihazı veya emülatörü döndürerek ekran yönünü değiştirin. Mevcut puanın kaybolduğunu unutmayın.
  3. Oyunu birkaç kelime daha kullanarak açıklayın. Oyun ekranı bir skorla gösterildiğinde uygulamayı kapatıp yeniden açın. Uygulama durumu kaydedilmediği için oyunun baştan başladığını fark edeceksiniz.
  4. Oyunu birkaç kelimeyle oynayın, ardından Oyunu Bitir düğmesine dokunun. Hiçbir şey olmadığını fark edeceksiniz.

Uygulamadaki sorunlar:

  • Başlangıç uygulaması, yapılandırma değişiklikleri sırasında (ör. cihaz yönü değiştiğinde veya uygulama kapatılıp yeniden başlatıldığında) uygulama durumunu kaydetmez ve geri yüklemez.
    Bu sorunu onSaveInstanceState() geri çağırmasını kullanarak çözebilirsiniz. Ancak onSaveInstanceState() yöntemini kullanmak için durumu bir pakette kaydetmek ve bu durumu almak üzere mantığı uygulamak için ek kod yazmanız gerekir. Ayrıca, depolanabilecek veri miktarı da minimum düzeydedir.
  • Kullanıcı Oyunu Bitir düğmesine dokunduğunda oyun ekranı, skor ekranına gitmiyor.

Bu sorunları, bu codelab'de öğrendiğiniz uygulama mimarisi bileşenlerini kullanarak çözebilirsiniz.

Uygulama mimarisi

Uygulama mimarisi, uygulamalarınızın sınıflarını ve aralarındaki ilişkileri, kodun düzenli olması, belirli senaryolarda iyi performans göstermesi ve kolayca kullanılabilmesi için tasarlama yöntemidir. Bu dört codelab'den oluşan seride, GuessTheWord uygulamasında yaptığınız iyileştirmeler Android uygulama mimarisi yönergelerine uygun olur ve Android Architecture Components'ı kullanırsınız. Android uygulama mimarisi, MVVM (model-view-viewmodel) mimari desenine benzer.

GuessTheWord uygulaması, ilgi alanlarının ayrılması tasarım ilkesine uyar ve sınıflara ayrılır. Her sınıf ayrı bir ilgi alanını ele alır. Dersin bu ilk codelab'inde, çalıştığınız sınıflar bir kullanıcı arayüzü denetleyicisi, bir ViewModel ve bir ViewModelFactory'dir.

Kullanıcı arayüzü denetleyicisi

Kullanıcı arayüzü denetleyicisi, Activity veya Fragment gibi kullanıcı arayüzü tabanlı bir sınıftır. Bir kullanıcı arayüzü denetleyicisi yalnızca görünümleri görüntüleme ve kullanıcı girişini yakalama gibi kullanıcı arayüzü ve işletim sistemi etkileşimlerini işleyen mantığı içermelidir. Gösterilecek metni belirleyen mantık gibi karar verme mantığını kullanıcı arayüzü denetleyicisine yerleştirmeyin.

GuessTheWord başlangıç kodunda, kullanıcı arayüzü denetleyicileri üç parçadır: GameFragment, ScoreFragment, ve TitleFragment. "İlgi alanlarının ayrılması" tasarım ilkesine göre GameFragment yalnızca oyun öğelerini ekrana çizmekten ve kullanıcının düğmelere ne zaman dokunduğunu bilmekten sorumludur. Kullanıcı bir düğmeye dokunduğunda bu bilgiler GameViewModel'ya iletilir.

ViewModel

Bir ViewModel, ViewModel ile ilişkili bir parçada veya etkinlikte gösterilecek verileri tutar. Bir ViewModel, verilerin kullanıcı arayüzü denetleyicisi tarafından görüntülenmeye hazırlanması için veriler üzerinde basit hesaplamalar ve dönüşümler yapabilir. Bu mimaride ViewModel karar verme işlemini gerçekleştirir.

GameViewModel, ekranda gösterilecek veriler olduğu için puan değeri, kelime listesi ve mevcut kelime gibi verileri tutar. GameViewModel, verilerin mevcut durumuna karar vermek için basit hesaplamalar yapmaya yönelik iş mantığını da içerir.

ViewModelFactory

ViewModelFactory, oluşturucu parametreleriyle veya parametreler olmadan ViewModel nesnelerini oluşturur.

Sonraki codelab'lerde, kullanıcı arayüzü denetleyicileri ve ViewModel ile ilgili diğer Android Architecture Components hakkında bilgi edineceksiniz.

ViewModel sınıfı, kullanıcı arayüzüyle ilgili verileri depolamak ve yönetmek için tasarlanmıştır. Bu uygulamada her ViewModel bir parçayla ilişkilendirilir.

Bu görevde, uygulamanıza ilk ViewModel öğenizi (GameFragment için GameViewModel) eklersiniz. Ayrıca ViewModel simgesinin yaşam döngüsüne duyarlı olmasının ne anlama geldiğini de öğrenirsiniz.

1. adım: GameViewModel sınıfını ekleyin

  1. build.gradle(module:app) dosyasını açın. dependencies bloğunun içine ViewModel için Gradle bağımlılığını ekleyin.

    Kitaplığın en son sürümünü kullanırsanız çözüm uygulaması beklendiği gibi derlenir. Çalışmıyorsa sorunu çözmeyi deneyin veya aşağıda gösterilen sürüme geri dönün.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  1. screens/game/ paket klasöründe GameViewModel adlı yeni bir Kotlin sınıfı oluşturun.
  2. GameViewModel sınıfının ViewModel soyut sınıfını genişletmesini sağlayın.
  3. ViewModel'ın yaşam döngüsüne nasıl duyarlı olduğunu daha iyi anlamanıza yardımcı olmak için log ifadesi içeren bir init bloğu ekleyin.
class GameViewModel : ViewModel() {
   init {
       Log.i("GameViewModel", "GameViewModel created!")
   }
}

2. adım: onCleared() işlevini geçersiz kılın ve günlük kaydı ekleyin

İlişkili parça ayrıldığında veya etkinlik tamamlandığında ViewModel yok edilir. ViewModel yok edilmeden hemen önce kaynakları temizlemek için onCleared() geri çağırma işlevi çağrılır.

  1. GameViewModel sınıfında onCleared() yöntemini geçersiz kılın.
  2. onCleared() içine bir günlük ifadesi ekleyerek GameViewModel yaşam döngüsünü izleyin.
override fun onCleared() {
   super.onCleared()
   Log.i("GameViewModel", "GameViewModel destroyed!")
}

3. adım: GameViewModel'ı oyun parçasıyla ilişkilendirin

Bir ViewModel, kullanıcı arayüzü denetleyicisiyle ilişkilendirilmelidir. İkisini ilişkilendirmek için kullanıcı arayüzü denetleyicisinde ViewModel öğesine bir referans oluşturursunuz.

Bu adımda, ilgili kullanıcı arayüzü denetleyicisi olan GameFragment içinde GameViewModel öğesinin referansını oluşturursunuz.

  1. GameFragment sınıfında, üst düzeyde sınıf değişkeni olarak GameViewModel türünde bir alan ekleyin.
private lateinit var viewModel: GameViewModel

4. adım: ViewModel'i başlatın

Ekran döndürme gibi yapılandırma değişiklikleri sırasında parçalar gibi kullanıcı arayüzü denetleyicileri yeniden oluşturulur. Ancak ViewModel örnekleri kalır. ViewModel sınıfını kullanarak ViewModel örneğini oluşturursanız parça her yeniden oluşturulduğunda yeni bir nesne oluşturulur. Bunun yerine, ViewModelProvider kullanarak ViewModel örneğini oluşturun.

ViewModelProvider nasıl çalışır?

  • ViewModelProvider, mevcut bir ViewModel varsa onu döndürür, yoksa yeni bir ViewModel oluşturur.
  • ViewModelProvider, verilen kapsamla (bir etkinlik veya bir parça) ilişkili bir ViewModel örneği oluşturur.
  • Oluşturulan ViewModel, kapsam geçerli olduğu sürece saklanır. Örneğin, kapsam bir parçaysa ViewModel, parça ayrılana kadar korunur.

ViewModel öğesini başlatın. ViewModelProviders.of() yöntemini kullanarak ViewModelProvider oluşturun:

  1. GameFragment sınıfında viewModel değişkenini başlatın. Bu kodu onCreateView() içine, bağlama değişkeninin tanımından sonra yerleştirin. ViewModelProviders.of() yöntemini kullanın ve ilişkili GameFragment bağlamını ve GameViewModel sınıfını iletin.
  2. ViewModel nesnesinin başlatılmasının üstüne, ViewModelProviders.of() yöntem çağrısını günlüğe kaydetmek için bir günlük ifadesi ekleyin.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
  1. Uygulamayı çalıştırın. Android Studio'da Logcat bölmesini açın ve Game ile filtreleyin. Cihazınızda veya emülatörünüzde Oynat düğmesine dokunun. Oyun ekranı açılır.

    Logcat'te gösterildiği gibi, GameFragment öğesinin onCreateView() yöntemi, GameViewModel öğesini oluşturmak için ViewModelProviders.of() yöntemini çağırır. GameFragment ve GameViewModel öğesine eklediğiniz günlük kaydı ifadeleri Logcat'te gösterilir.

  1. Cihazınızda veya emülatörünüzde otomatik döndürme ayarını etkinleştirin ve ekran yönünü birkaç kez değiştirin. GameFragment her seferinde yok edilip yeniden oluşturulduğundan ViewModelProviders.of() her seferinde çağrılır. Ancak GameViewModel yalnızca bir kez oluşturulur ve her çağrı için yeniden oluşturulmaz veya yok edilmez.
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
  1. Oyundan çıkın veya oyun parçasından çıkın. GameFragment yok edildi. İlişkili GameViewModel de yok edilir ve geri çağırma onCleared() çağrılır.
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel destroyed!

ViewModel, yapılandırma değişikliklerinden etkilenmez. Bu nedenle, yapılandırma değişikliklerinden etkilenmemesi gereken veriler için iyi bir yerdir:

  • Ekranda gösterilecek verileri ve bu verileri işleyecek kodu ViewModel içine yerleştirin.
  • Etkinlikler, parçalar ve görünümler yapılandırma değişikliklerinden etkilenmediği için ViewModel hiçbir zaman parçalara, etkinliklere veya görünümlere referans içermemelidir.

Karşılaştırma için, GameFragment kullanıcı arayüzü verilerinin ViewModel eklenmeden önce ve eklendikten sonra başlangıç uygulamasında nasıl işlendiği aşağıda açıklanmıştır:ViewModel

  • ViewModel eklemeden önce:
    Uygulamanın ekran döndürme gibi bir yapılandırma değişikliği geçirdiği sırada oyun parçası yok edilir ve yeniden oluşturulur. Veriler kaybolur.
  • ViewModel ekleyip oyun fragmentinin kullanıcı arayüzü verilerini ViewModel içine taşıdıktan sonra:
    Fragmentin göstermesi gereken tüm veriler artık ViewModel. Uygulama yapılandırma değişikliği geçirdiğinde ViewModel korunur ve veriler saklanır.

Bu görevde, uygulamanın kullanıcı arayüzü verilerini, verileri işleme yöntemleriyle birlikte GameViewModel sınıfına taşırsınız. Bunu, yapılandırma değişiklikleri sırasında verilerin saklanması için yaparsınız.

1. adım: Veri alanlarını ve veri işlemeyi ViewModel'e taşıyın

Aşağıdaki veri alanlarını ve yöntemlerini GameFragment konumundan GameViewModel konumuna taşıyın:

  1. word, score ve wordList veri alanlarını taşıyın. word ve score öğelerinin private olmadığından emin olun.

    Görünümlere referans içerdiğinden bağlama değişkeni GameFragmentBinding taşınmamalıdır. Bu değişken, düzeni genişletmek, tıklama dinleyicilerini ayarlamak ve verileri ekranda göstermek için kullanılır. Bunlar, parçanın sorumluluklarıdır.
  2. resetList() ve nextWord() yöntemlerini taşıyın. Bu yöntemler, ekranda hangi kelimenin gösterileceğine karar verir.
  3. onCreateView() yönteminin içinden, yöntem çağrılarını resetList() ve nextWord() ile GameViewModel'in init bloğuna taşıyın.

    Bu yöntemler init bloğunda olmalıdır. Bunun nedeni, ViewModel oluşturulduğunda kelime listesini sıfırlamanız gerektiğidir. Parça her oluşturulduğunda sıfırlamanız gerekmez. Günlük ifadesini GameFragment öğesinin init bloğunda silebilirsiniz.

GameFragment içindeki onSkip() ve onCorrect() tıklama işleyicileri, verileri işleme ve kullanıcı arayüzünü güncelleme kodunu içerir. Kullanıcı arayüzünü güncelleme kodu parçada kalmalı ancak verileri işleme kodu ViewModel taşınmalıdır.

Şimdilik, her iki yere de aynı yöntemleri yerleştirin:

  1. onSkip() ve onCorrect() yöntemlerini GameFragment'den GameViewModel'ye kopyalayın.
  2. GameViewModel içinde, onSkip() ve onCorrect() yöntemlerinin private olmadığından emin olun. Çünkü bu yöntemlere parçadan referans vereceksiniz.

Yeniden düzenlemeden sonra GameViewModel sınıfının kodu:

class GameViewModel : ViewModel() {
   // The current word
   var word = ""
   // The current score
   var score = 0
   // The list of words - the front of the list is the next word to guess
   private lateinit var wordList: MutableList<String>

   /**
    * Resets the list of words and randomizes the order
    */
   private fun resetList() {
       wordList = mutableListOf(
               "queen",
               "hospital",
               "basketball",
               "cat",
               "change",
               "snail",
               "soup",
               "calendar",
               "sad",
               "desk",
               "guitar",
               "home",
               "railway",
               "zebra",
               "jelly",
               "car",
               "crow",
               "trade",
               "bag",
               "roll",
               "bubble"
       )
       wordList.shuffle()
   }

   init {
       resetList()
       nextWord()
       Log.i("GameViewModel", "GameViewModel created!")
   }
   /**
    * Moves to the next word in the list
    */
   private fun nextWord() {
       if (!wordList.isEmpty()) {
           //Select and remove a word from the list
           word = wordList.removeAt(0)
       }
       updateWordText()
       updateScoreText()
   }
 /** Methods for buttons presses **/
   fun onSkip() {
       if (!wordList.isEmpty()) {
           score--
       }
       nextWord()
   }

   fun onCorrect() {
       if (!wordList.isEmpty()) {
           score++
       }
       nextWord()
   }

   override fun onCleared() {
       super.onCleared()
       Log.i("GameViewModel", "GameViewModel destroyed!")
   }
}

Aşağıda, yeniden düzenlemeden sonraki GameFragment sınıfının kodu verilmiştir:

/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {


   private lateinit var binding: GameFragmentBinding


   private lateinit var viewModel: GameViewModel


   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {

       // Inflate view and obtain an instance of the binding class
       binding = DataBindingUtil.inflate(
               inflater,
               R.layout.game_fragment,
               container,
               false
       )

       Log.i("GameFragment", "Called ViewModelProviders.of")
       viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)

       binding.correctButton.setOnClickListener { onCorrect() }
       binding.skipButton.setOnClickListener { onSkip() }
       updateScoreText()
       updateWordText()
       return binding.root

   }


   /** Methods for button click handlers **/

   private fun onSkip() {
       if (!wordList.isEmpty()) {
           score--
       }
       nextWord()
   }

   private fun onCorrect() {
       if (!wordList.isEmpty()) {
           score++
       }
       nextWord()
   }


   /** Methods for updating the UI **/

   private fun updateWordText() {
       binding.wordText.text = word
   }

   private fun updateScoreText() {
       binding.scoreText.text = score.toString()
   }
}

2. adım: GameFragment'teki tıklama işleyicileri ve veri alanlarıyla ilgili referansları güncelleyin

  1. GameFragment bölümünde onSkip() ve onCorrect() yöntemlerini güncelleyin. Puanı güncellemek için kodu kaldırın ve bunun yerine viewModel üzerinde ilgili onSkip() ve onCorrect() yöntemlerini çağırın.
  2. nextWord() yöntemini ViewModel'ye taşıdığınız için oyun parçası artık bu yönteme erişemiyor.

    GameFragment'deki onSkip() ve onCorrect() yöntemlerinde nextWord() çağrısını updateScoreText() ve updateWordText() ile değiştirin. Bu yöntemler, verileri ekranda gösterir.
private fun onSkip() {
   viewModel.onSkip()
   updateWordText()
   updateScoreText()
}
private fun onCorrect() {
   viewModel.onCorrect()
   updateScoreText()
   updateWordText()
}
  1. GameFragment içinde, score ve word değişkenlerini GameViewModel değişkenlerini kullanacak şekilde güncelleyin. Çünkü bu değişkenler artık GameViewModel içinde yer alıyor.
private fun updateWordText() {
   binding.wordText.text = viewModel.word
}

private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.toString()
}
  1. GameViewModel içinde, nextWord() yönteminde updateWordText() ve updateScoreText() yöntemlerine yapılan çağrıları kaldırın. Bu yöntemler artık GameFragment üzerinden çağrılıyor.
  2. Uygulamayı oluşturun ve hata olmadığından emin olun. Hata varsa projeyi temizleyip yeniden oluşturun.
  3. Uygulamayı çalıştırın ve oyunu bazı kelimelerle oynayın. Oyun ekranındayken cihazı döndürün. Yön değişikliğinden sonra mevcut puanın ve mevcut kelimenin korunduğuna dikkat edin.

Tebrikler! Artık uygulamanızın tüm verileri ViewModel içinde depolandığı için yapılandırma değişiklikleri sırasında korunur.

Bu görevde, End Game (Oyunu Bitir) düğmesi için tıklama işleyiciyi uygulayacaksınız.

  1. GameFragment içinde onEndGame() adlı bir yöntem ekleyin. Kullanıcı Oyunu Bitir düğmesine dokunduğunda onEndGame() yöntemi çağrılır.
private fun onEndGame() {
   }
  1. GameFragment içinde, onCreateView() yönteminde Anladım ve Atla düğmeleri için tıklama dinleyicilerini ayarlayan kodu bulun. Bu iki satırın hemen altında, End Game (Oyunu Bitir) düğmesi için bir tıklama işleyicisi ayarlayın. Bağlama değişkenini (binding) kullanın. Tıklama işleyicisinin içinde onEndGame() yöntemini çağırın.
binding.endGameButton.setOnClickListener { onEndGame() }
  1. GameFragment içinde, uygulamada puan ekranına gitmek için gameFinished() adlı bir yöntem ekleyin. Safe Args'ı kullanarak puanı bağımsız değişken olarak iletin.
/**
* Called when the game is finished
*/
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score
   NavHostFragment.findNavController(this).navigate(action)
}
  1. onEndGame() yönteminde gameFinished() yöntemini çağırın.
private fun onEndGame() {
   gameFinished()
}
  1. Uygulamayı çalıştırın, oyunu oynayın ve bazı kelimeler arasında geçiş yapın. Oyunu Bitir düğmesine dokunun. Uygulamanın puan ekranına gittiğini ancak nihai puanın gösterilmediğini fark edin. Bunu bir sonraki görevde düzelteceksiniz.

Kullanıcı oyunu bitirdiğinde ScoreFragment puanı göstermez. ScoreFragment tarafından gösterilecek puanı tutacak bir ViewModel istiyorsunuz. Fabrika yöntemi kalıbını kullanarak ViewModel başlatma sırasında puan değerini iletirsiniz.

Fabrika yöntemi deseni, nesne oluşturmak için fabrika yöntemlerini kullanan bir yapısal tasarım desenidir. Fabrika yöntemi, aynı sınıfın bir örneğini döndüren bir yöntemdir.

Bu görevde, puan parçası için parametreli bir oluşturucu ve ViewModel öğesini oluşturmak için bir fabrika yöntemi içeren bir ViewModel oluşturacaksınız.

  1. score paketi altında ScoreViewModel adlı yeni bir Kotlin sınıfı oluşturun. Bu sınıf, puan fragment'ı için ViewModel olacak.
  2. ScoreViewModel sınıfını ViewModel. sınıfından genişletin. Son puan için bir oluşturucu parametresi ekleyin. Günlük ifadesi içeren bir init bloğu ekleyin.
  3. ScoreViewModel sınıfına, son puanı kaydetmek için score adlı bir değişken ekleyin.
class ScoreViewModel(finalScore: Int) : ViewModel() {
   // The final score
   var score = finalScore
   init {
       Log.i("ScoreViewModel", "Final score is $finalScore")
   }
}
  1. score paketi altında ScoreViewModelFactory adlı başka bir Kotlin sınıfı oluşturun. Bu sınıf, ScoreViewModel nesnesinin oluşturulmasından sorumludur.
  2. ScoreViewModelFactory dersini ViewModelProvider.Factory tarihinden itibaren uzatın. Nihai skor için bir oluşturucu parametresi ekleyin.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
  1. ScoreViewModelFactory içinde Android Studio, uygulanmamış bir soyut üye hakkında hata gösteriyor. Hatayı düzeltmek için create() yöntemini geçersiz kılın. create() yönteminde, yeni oluşturulan ScoreViewModel nesnesini döndürün.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
   if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
       return ScoreViewModel(finalScore) as T
   }
   throw IllegalArgumentException("Unknown ViewModel class")
}
  1. ScoreFragment içinde ScoreViewModel ve ScoreViewModelFactory için sınıf değişkenleri oluşturun.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
  1. ScoreFragment içinde, onCreateView() içinde, binding değişkenini başlatma işleminden sonra viewModelFactory değişkenini başlatın. ScoreViewModelFactory kullanın. Nihai puanı, bağımsız değişken paketinden ScoreViewModelFactory() için oluşturucu parametresi olarak iletin.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
  1. onCreateView( içinde, viewModelFactory başlatıldıktan sonra viewModel nesnesini başlatın. ViewModelProviders.of() yöntemini çağırın, ilişkili puan parçası bağlamını ve viewModelFactory öğesini iletin. Bu işlem, viewModelFactory sınıfında tanımlanan fabrika yöntemi kullanılarak ScoreViewModel nesnesini oluşturur..
viewModel = ViewModelProviders.of(this, viewModelFactory)
       .get(ScoreViewModel::class.java)
  1. onCreateView() yönteminde, viewModel başlatıldıktan sonra scoreText görünümünün metnini ScoreViewModel içinde tanımlanan nihai puana ayarlayın.
binding.scoreText.text = viewModel.score.toString()
  1. Uygulamanızı çalıştırın ve oyunu oynayın. Kelimelerin bir kısmını veya tamamını inceleyip Oyunu Bitir'e dokunun. Puan snippet'inde artık nihai puanın gösterildiğini fark edeceksiniz.

  1. İsteğe bağlı: ScoreViewModel üzerinde filtreleme yaparak Logcat'teki ScoreViewModel günlüklerini kontrol edin. Puan değeri gösterilmelidir.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15

Bu görevde, ViewModel kullanmak için ScoreFragment uyguladınız. Ayrıca, ViewModelFactory arayüzünü kullanarak ViewModel için parametreli bir oluşturucu oluşturmayı da öğrendiniz.

Tebrikler! Uygulamanızın mimarisini, Android Architecture Components'tan birini kullanacak şekilde değiştirdiniz, ViewModel. Uygulamanın yaşam döngüsü sorununu çözdünüz ve artık oyunun verileri yapılandırma değişikliklerinden etkilenmiyor. Ayrıca, ViewModelFactory arayüzünü kullanarak ViewModel oluşturmak için parametreli bir oluşturucu oluşturmayı da öğrendiniz.

Android Studio projesi: GuessTheWord

  • Android uygulama mimarisi yönergelerinde, farklı sorumluluklara sahip sınıfların ayrılması önerilir.
  • Kullanıcı arayüzü denetleyicisi, Activity veya Fragment gibi kullanıcı arayüzü tabanlı bir sınıftır. Kullanıcı arayüzü denetleyicileri yalnızca kullanıcı arayüzü ve işletim sistemi etkileşimlerini işleyen mantığı içermeli, kullanıcı arayüzünde gösterilecek verileri içermemelidir. Bu verileri ViewModel içine yerleştirin.
  • ViewModel sınıfı, kullanıcı arayüzüyle ilgili verileri depolar ve yönetir. ViewModel sınıfı, verilerin ekran döndürme gibi yapılandırma değişikliklerinden etkilenmemesini sağlar.
  • ViewModel, önerilen Android Mimari Bileşenleri'nden biridir.
  • ViewModelProvider.Factory, ViewModel nesnesi oluşturmak için kullanabileceğiniz bir arayüzdür.

Aşağıdaki tabloda, kullanıcı arayüzü denetleyicileri ile bunlar için veri tutan ViewModel örnekleri karşılaştırılmaktadır:

Kullanıcı arayüzü denetleyicisi

ViewModel

Bu codelab'de oluşturduğunuz ScoreFragment, kullanıcı arayüzü denetleyicisine bir örnektir.

ViewModel örneği, bu codelab'de oluşturduğunuz ScoreViewModel'dir.

Kullanıcı arayüzünde gösterilecek veri içermiyor.

Kullanıcı arayüzü denetleyicisinin kullanıcı arayüzünde gösterdiği verileri içerir.

Verileri görüntüleme kodu ve tıklama dinleyicileri gibi kullanıcı etkinliği kodu içerir.

Veri işleme için kod içerir.

Her yapılandırma değişikliği sırasında yok edilir ve yeniden oluşturulur.

Yalnızca ilişkili kullanıcı arayüzü denetleyicisi kalıcı olarak kaldırıldığında (ör. bir etkinlik için etkinlik tamamlandığında veya bir parça için parça ayrıldığında) yok edilir.

Görünümler içerir.

Yapılandırma değişikliklerinden sonra etkinlikler, parçalar veya görünümler korunmaz ancak ViewModel korunur. Bu nedenle, hiçbir zaman etkinliklere, parçalara veya görünümlere referans içermemelidir.

İlişkili ViewModel öğesine referans içerir.

İlişkili kullanıcı arayüzü denetleyicisine herhangi bir referans içermez.

Udacity kursu:

Android geliştirici belgeleri:

Diğer:

Bu bölümde, bir eğitmenin yönettiği kurs kapsamında bu codelab'i tamamlayan öğrenciler için olası ödevler listelenmektedir. Eğitmen, aşağıdakileri yapmalıdır:

  • Gerekirse ödev atayın.
  • Öğrencilere ev ödevi ödevlerini nasıl göndereceklerini bildirin.
  • Ödevlere not verin.

Eğitmenler bu önerileri istedikleri kadar kullanabilir ve uygun olduğunu düşündükleri diğer ödevleri verebilirler.

Bu codelab'i kendi başınıza tamamlıyorsanız bilginizi test etmek için bu ödevleri kullanabilirsiniz.

Bu soruları yanıtlayın

1. Soru

Cihaz yapılandırması değişikliği sırasında veri kaybını önlemek için uygulama verilerini hangi sınıfa kaydetmelisiniz?

  • ViewModel
  • LiveData
  • Fragment
  • Activity

2. Soru

Bir ViewModel hiçbir zaman parçalara, etkinliklere veya görünümlere referans içermemelidir. Doğru mu yanlış mı?

  • Doğru
  • Yanlış

3. Soru

ViewModel ne zaman yok edilir?

  • İlişkili kullanıcı arayüzü denetleyicisi, cihaz yönü değişikliği sırasında yok edilip yeniden oluşturulduğunda.
  • Yön değişikliğinde
  • İlişkili kullanıcı arayüzü denetleyicisi tamamlandığında (etkinlikse) veya ayrıldığında (parçaysa).
  • Kullanıcı Geri düğmesine bastığında.

4. Soru

ViewModelFactory arayüzü ne için kullanılır?

  • ViewModel nesnesi oluşturma.
  • Yön değişiklikleri sırasında verileri koruma.
  • Ekranda gösterilen veriler yenilenir.
  • Uygulama verileri değiştirildiğinde bildirim alma

Sonraki derse başlayın: 5.2: LiveData ve LiveData gözlemcileri

Bu kurstaki diğer codelab'lerin bağlantılarını Android Kotlin Hakkında Temel Bilgiler codelab'leri açılış sayfasında bulabilirsiniz.