Android Kotlin Fundamentals 08.2: Loading and displaying images from the internet

Ten moduł Codelab jest częścią kursu Android Kotlin Fundamentals. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej kursu Android Kotlin Fundamentals.

Wprowadzenie

W poprzednich ćwiczeniach z programowania dowiedzieliśmy się, jak pobierać dane z usługi internetowej i analizować odpowiedź, aby uzyskać obiekt danych. W tym ćwiczeniu wykorzystasz tę wiedzę, aby wczytywać i wyświetlać zdjęcia z adresu URL. Przypomnisz sobie też, jak utworzyć RecyclerView i użyć go do wyświetlania siatki obrazów na stronie przeglądu.

Co warto wiedzieć

  • Jak tworzyć i używać fragmentów.
  • Jak używać komponentów architektury, w tym modeli widoku, fabryk modeli widoku, przekształceń i LiveData.
  • Dowiedz się, jak pobierać dane JSON z usługi internetowej REST i analizować je w obiektach Kotlin za pomocą bibliotek RetrofitMoshi.
  • Jak utworzyć układ siatki za pomocą elementu RecyclerView.
  • Jak działają Adapter, ViewHolderDiffUtil.

Czego się nauczysz

  • Jak używać biblioteki Glide do wczytywania i wyświetlania obrazu z adresu URL.
  • Jak używać RecyclerView i adaptera siatki do wyświetlania siatki obrazów.
  • Jak radzić sobie z potencjalnymi błędami podczas pobierania i wyświetlania obrazów.

Co musisz zrobić

  • Zmodyfikuj aplikację MarsRealEstate, aby pobierać adres URL obrazu z danych nieruchomości na Marsie, a następnie użyj Glide do wczytania i wyświetlenia tego obrazu.
  • Dodaj do aplikacji animację ładowania i ikonę błędu.
  • Użyj elementu RecyclerView, aby wyświetlić siatkę obrazów nieruchomości na Marsie.
  • Dodaj do elementu RecyclerView obsługę stanu i błędów.

W tym ćwiczeniu z programowania (i powiązanych z nim ćwiczeniach) będziesz pracować z aplikacją MarsRealEstate, która wyświetla nieruchomości na sprzedaż na Marsie. Aplikacja łączy się z serwerem internetowym, aby pobierać i wyświetlać dane dotyczące nieruchomości, w tym szczegóły takie jak cena i informacje o tym, czy nieruchomość jest dostępna na sprzedaż lub wynajem. Obrazy przedstawiające poszczególne nieruchomości to prawdziwe zdjęcia z Marsa wykonane przez łaziki NASA.

Wersja aplikacji, którą utworzysz w ramach tego ćwiczenia z programowania, wypełni stronę przeglądu, na której wyświetlana jest siatka obrazów. Obrazy są częścią danych usługi, które aplikacja pobiera z usługi internetowej dotyczącej nieruchomości na Marsie. Aplikacja będzie używać biblioteki Glide do wczytywania i wyświetlania obrazów oraz RecyclerView do tworzenia układu siatki obrazów. Aplikacja będzie też prawidłowo obsługiwać błędy sieci.

Wyświetlanie zdjęcia z adresu URL w internecie może wydawać się proste, ale wymaga sporo pracy inżynierskiej, aby działało dobrze. Obraz musi zostać pobrany, zbuforowany i zdekodowany z formatu skompresowanego do formatu, który może być używany przez Androida. Obraz powinien być przechowywany w pamięci podręcznej w pamięci, w pamięci podręcznej opartej na pamięci masowej lub w obu tych miejscach. Wszystko to musi się odbywać w wątkach w tle o niskim priorytecie, aby interfejs użytkownika pozostawał responsywny. Aby uzyskać najlepszą wydajność sieci i procesora, warto też pobierać i dekodować więcej niż 1 obraz naraz. Skuteczne wczytywanie obrazów z sieci to temat na osobne ćwiczenia z programowania.

Na szczęście możesz użyć biblioteki opracowanej przez społeczność o nazwie Glide, aby pobierać, buforować, dekodować i buforować obrazy. Glide znacznie ułatwia pracę w porównaniu z sytuacją, w której musisz wszystko robić od zera.

Glide potrzebuje w zasadzie 2 rzeczy:

  • Adres URL obrazu, który chcesz wczytać i wyświetlić.
  • Obiekt ImageView do wyświetlania tego obrazu.

W tym zadaniu dowiesz się, jak używać Glide do wyświetlania pojedynczego obrazu z usługi internetowej dotyczącej nieruchomości. Wyświetlasz obraz przedstawiający pierwszą nieruchomość na Marsie na liście nieruchomości zwracanej przez usługę internetową. Oto zrzuty ekranu przed i po:

Krok 1. Dodaj zależność Glide

  1. Otwórz aplikację MarsRealEstate z ostatnich zajęć. (Jeśli nie masz tej aplikacji, możesz ją pobrać MarsRealEstateNetwork).
  2. Uruchom aplikację, aby sprawdzić, co robi. (Wyświetla szczegóły tekstowe nieruchomości, która hipotetycznie jest dostępna na Marsie).
  3. Otwórz plik build.gradle (Module: app).
  4. W sekcji dependencies dodaj ten wiersz dla biblioteki Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"


Zwróć uwagę, że numer wersji jest już zdefiniowany osobno w pliku Gradle projektu.

  1. Kliknij Synchronizuj teraz, aby ponownie utworzyć projekt z nową zależnością.

Krok 2. Zaktualizuj model widoku

Następnie zaktualizuj klasę OverviewViewModel, aby uwzględnić dane na żywo dotyczące jednej usługi Mars.

  1. Otwórz pokój overview/OverviewViewModel.kt. Tuż pod ikoną LiveData dla _response dodaj zarówno wewnętrzne (zmienne), jak i zewnętrzne (niezmienne) dane na żywo dla jednego obiektu MarsProperty.

    Gdy pojawi się prośba, zaimportuj zajęcia MarsProperty (com.example.android.marsrealestate.network.MarsProperty).
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. W metodzie getMarsRealEstateProperties() znajdź wiersz w bloku try/catch {}, który ustawia wartość _response.value na liczbę właściwości. Dodaj test pokazany poniżej. Jeśli dostępne są obiekty MarsProperty, ten test ustawia wartość _property LiveData na pierwszą właściwość w listResult.
if (listResult.size > 0) {   
    _property.value = listResult[0]
}

Pełny blok try/catch {} wygląda teraz tak:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   if (listResult.size > 0) {      
       _property.value = listResult[0]
   }
 } catch (e: Exception) {
    _response.value = "Failure: ${e.message}"
 }
  1. Otwórz plik res/layout/fragment_overview.xml. W elemencie <TextView> zmień android:text, aby powiązać go z komponentem imgSrcUrl elementu property LiveData:
android:text="@{viewModel.property.imgSrcUrl}"
  1. Uruchom aplikację. W pierwszej usłudze Mars wyświetli się tylko adres URL obrazu.TextView Do tej pory udało Ci się tylko skonfigurować model widoku i dane na żywo dla tego adresu URL.

Krok 3. Utwórz adapter powiązania i wywołaj Glide

Masz już URL obrazu do wyświetlenia, więc możesz zacząć korzystać z Glide, aby go wczytać. W tym kroku użyjesz adaptera powiązania, aby pobrać adres URL z atrybutu XML powiązanego z elementem ImageView, a następnie użyjesz biblioteki Glide do wczytania obrazu. Adaptery powiązań to metody rozszerzające, które znajdują się między widokiem a powiązanymi danymi i zapewniają niestandardowe działanie, gdy dane ulegają zmianie. W tym przypadku niestandardowe działanie polega na wywołaniu Glide w celu wczytania obrazu z adresu URL do elementu ImageView.

  1. Otwórz pokój BindingAdapters.kt. Ten plik będzie zawierać adaptery powiązań, których używasz w całej aplikacji.
  2. Utwórz funkcję bindImage(), która przyjmuje parametry ImageViewString. Dodaj do funkcji adnotację @BindingAdapter. Adnotacja @BindingAdapter informuje o wiązaniu danych, że ten adapter wiązania ma być wykonywany, gdy element XML ma atrybut imageUrl.

    Zaimportuj androidx.databinding.BindingAdapterandroid.widget.ImageView, gdy pojawi się prośba.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. Wewnątrz funkcji bindImage() dodaj blok let {} dla argumentu imgUrl:
imgUrl?.let { 
}
  1. W bloku let {} dodaj wiersz widoczny poniżej, aby przekonwertować ciąg URL (z pliku XML) na obiekt Uri. Importuj androidx.core.net.toUri na żądanie.

    Chcesz, aby końcowy obiekt Uri używał schematu HTTPS, ponieważ serwer, z którego pobierasz obrazy, wymaga tego schematu. Aby użyć schematu HTTPS, dodaj buildUpon.scheme("https") do konstruktora toUri. Metoda toUri() to funkcja rozszerzenia Kotlina z biblioteki podstawowej Android KTX, więc wygląda, jakby była częścią klasy String.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. W funkcji let {} wywołaj funkcję Glide.with(), aby wczytać obraz z obiektu Uri do obiektu ImageView. W razie potrzeby zaimportuj com.bumptech.glide.Glide.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

Krok 4. Zaktualizuj układ i fragmenty

Chociaż Glide załadował obraz, nie widać jeszcze niczego. Następnym krokiem jest zaktualizowanie układu i fragmentów za pomocą elementu ImageView, aby wyświetlić obraz.

  1. Otwórz pokój res/layout/gridview_item.xml. Jest to plik zasobu układu, którego użyjesz w przypadku każdego elementu w RecyclerView w dalszej części tego kursu. Używasz go tymczasowo, aby wyświetlić tylko jeden obraz.
  2. Nad elementem <ImageView> dodaj element <data> dla wiązania danych i powiąż go z klasą OverviewViewModel:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. Dodaj atrybut app:imageUrl do elementu ImageView, aby użyć nowego adaptera powiązania ładowania obrazu:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. Otwórz pokój overview/OverviewFragment.kt. W metodzie onCreateView() zakomentuj wiersz, który powiększa klasę FragmentOverviewBinding i przypisuje ją do zmiennej powiązania. To tylko tymczasowe działanie. Później wrócisz do tego kroku.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. Zamiast tego dodaj wiersz, aby zwiększyć liczbę uczniów w klasie GridViewItemBinding. W razie potrzeby zaimportuj com.example.android.marsrealestate. databinding.GridViewItemBinding.
val binding = GridViewItemBinding.inflate(inflater)
  1. Uruchom aplikację. Na liście wyników powinna się teraz pojawić fotografia obrazu z pierwszego MarsProperty.

Krok 5. Dodaj proste obrazy ładowania i błędów

Glide może poprawić wrażenia użytkownika, wyświetlając obraz zastępczy podczas ładowania obrazu i obraz błędu, jeśli ładowanie się nie powiedzie, np. gdy obrazu brakuje lub jest uszkodzony. W tym kroku dodasz tę funkcję do adaptera powiązania i układu.

  1. Otwórz res/drawable/ic_broken_image.xml i po prawej stronie kliknij kartę Podgląd. W przypadku obrazu błędu używasz ikony uszkodzonego obrazu dostępnej w wbudowanej bibliotece ikon. Ten obiekt rysowalny wektorowo używa atrybutu android:tint, aby pokolorować ikonę na szaro.

  1. Otwórz pokój res/drawable/loading_animation.xml. Ten obiekt rysowalny to animacja zdefiniowana za pomocą tagu <animate-rotate>. Animacja obraca obraz loading_img.xml wokół punktu środkowego. (Animacja nie jest widoczna w podglądzie).

  1. Wróć do pliku BindingAdapters.kt. W metodzie bindImage() zmień wywołanie funkcji Glide.with() tak, aby wywoływała funkcję apply() między load() a into(). Importuj com.bumptech.glide.request.RequestOptions na żądanie.

    Ten kod ustawia obraz zastępczy, który ma być używany podczas wczytywania (element rysowalny loading_animation). Kod ustawia też obraz, który ma być używany, jeśli ładowanie obrazu się nie powiedzie (element rysowalny broken_image). Pełna metoda bindImage() wygląda teraz tak:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = 
           imgUrl.toUri().buildUpon().scheme("https").build()
        Glide.with(imgView.context)
                .load(imgUri)
                .apply(RequestOptions()
                        .placeholder(R.drawable.loading_animation)
                        .error(R.drawable.ic_broken_image))
                .into(imgView)
    }
}
  1. Uruchom aplikację. W zależności od szybkości połączenia sieciowego może się na chwilę pojawić obraz wczytywania, gdy Glide pobiera i wyświetla obraz nieruchomości. Nie zobaczysz jeszcze ikony uszkodzonego obrazu, nawet jeśli wyłączysz sieć – naprawisz to w ostatniej części tego laboratorium.

Aplikacja wczytuje teraz informacje o nieruchomości z internetu. Na podstawie danych z pierwszego elementu listy MarsProperty utworzono właściwość LiveData w modelu widoku, a adres URL obrazu z danych tej właściwości został użyty do wypełnienia elementu ImageView. Celem jest jednak wyświetlenie w aplikacji siatki obrazów, więc musisz użyć elementu RecyclerView z elementem GridLayoutManager.

Krok 1. Zaktualizuj model widoku

Obecnie model widoku ma _property LiveData, który zawiera 1 obiekt MarsProperty – pierwszy na liście odpowiedzi z usługi internetowej. W tym kroku zmienisz ten element LiveData, aby zawierał całą listę obiektów MarsProperty.

  1. Otwórz pokój overview/OverviewViewModel.kt.
  2. Zmień zmienną prywatną _property na _properties. Zmień typ na listę obiektów MarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Zastąp zewnętrzne dane bieżące property wartością properties. Dodaj listę do typu LiveData:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. Przewiń w dół do metody getMarsRealEstateProperties(). W bloku try {} zastąp cały test dodany w poprzednim zadaniu wierszem pokazanym poniżej. Ponieważ zmienna listResult zawiera listę obiektów MarsProperty, możesz po prostu przypisać ją do zmiennej _properties.value zamiast sprawdzać, czy odpowiedź jest prawidłowa.
_properties.value = listResult

Cały blok try/catch wygląda teraz tak:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   _properties.value = listResult
} catch (e: Exception) {
   _response.value = "Failure: ${e.message}"
}

Krok 2. Zaktualizuj układy i fragmenty

Następnym krokiem jest zmiana układu aplikacji i fragmentów, aby zamiast widoku pojedynczego obrazu używać widoku recyklera i układu siatki.

  1. Otwórz pokój res/layout/gridview_item.xml. Zmień powiązanie danych z OverviewViewModel na MarsProperty i zmień nazwę zmiennej na "property".
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. <ImageView> zmień atrybut app:imageUrl, aby odwoływał się do adresu URL obrazu w obiekcie MarsProperty:
app:imageUrl="@{property.imgSrcUrl}"
  1. Otwórz pokój overview/OverviewFragment.kt. W pliku onCreateview() odkomentuj wiersz, który powiększa FragmentOverviewBinding. Usuń lub zmień w komentarz wiersz, który zwiększa wartość GridViewBinding. Te zmiany cofną tymczasowe zmiany wprowadzone w ostatnim zadaniu.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. Otwórz pokój res/layout/fragment_overview.xml. Usuń cały element <TextView>.
  2. Zamiast tego dodaj ten element <RecyclerView>, który używa elementu GridLayoutManager i układu grid_view_item dla pojedynczego produktu:
<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/photos_grid"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:padding="6dp"
            android:clipToPadding="false"
            app:layoutManager=
               "androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="2"
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />

Krok 3. Dodaj adapter siatki zdjęć

Teraz układ fragment_overview ma RecyclerView, a układ grid_view_item ma 1 ImageView. W tym kroku powiążesz dane z RecyclerView za pomocą adaptera RecyclerView.

  1. Otwórz pokój overview/PhotoGridAdapter.kt.
  2. Utwórz klasę PhotoGridAdapter z parametrami konstruktora podanymi poniżej. Klasa PhotoGridAdapter rozszerza klasę ListAdapter, której konstruktor wymaga typu elementu listy, uchwytu widoku i implementacji DiffUtil.ItemCallback.

    Gdy pojawi się prośba, zaimportuj zajęcia androidx.recyclerview.widget.ListAdapter i com.example.android.marsrealestate.network.MarsProperty. W kolejnych krokach zaimplementujesz pozostałe brakujące części tego konstruktora, które powodują błędy.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. Kliknij dowolne miejsce w klasie PhotoGridAdapter i naciśnij Control+i, aby zaimplementować metody ListAdapter, czyli onCreateViewHolder() i onBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. Na końcu definicji klasy PhotoGridAdapter, po dodanych przed chwilą metodach, dodaj definicję obiektu towarzyszącego dla DiffCallback, jak pokazano poniżej.

    Zaimportuj androidx.recyclerview.widget.DiffUtil, gdy pojawi się prośba.

    Obiekt DiffCallback rozszerza obiekt DiffUtil.ItemCallback o typ obiektu, który chcesz porównać – MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. Naciśnij Control+i, aby zaimplementować metody komparatora dla tego obiektu, czyli areItemsTheSame()areContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. W przypadku metody areItemsTheSame() usuń TODO. Użyj operatora równości referencyjnej w języku Kotlin (===), który zwraca wartość true, jeśli odwołania do obiektów oldItemnewItem są takie same.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. W przypadku atrybutu areContentsTheSame() użyj standardowego operatora równości tylko w przypadku identyfikatora atrybutów oldItem i newItem.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. W klasie PhotoGridAdapter, pod obiektem towarzyszącym, dodaj definicję klasy wewnętrznej MarsPropertyViewHolder, która rozszerza klasę RecyclerView.ViewHolder.

    W razie potrzeby zaimportuj klasy androidx.recyclerview.widget.RecyclerViewcom.example.android.marsrealestate.databinding.GridViewItemBinding.

    Zmienna GridViewItemBinding jest potrzebna do powiązania klasy MarsProperty z układem, więc przekaż ją do klasy MarsPropertyViewHolder. Ponieważ klasa bazowa ViewHolder wymaga widoku w konstruktorze, przekazujesz do niej widok główny powiązania.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. MarsPropertyViewHolder utwórz metodę bind(), która przyjmuje obiekt MarsProperty jako argument i ustawia binding.property na ten obiekt. Wywołaj executePendingBindings() po ustawieniu właściwości, co spowoduje natychmiastowe wykonanie aktualizacji.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. W pliku onCreateViewHolder() usuń komentarz TODO i dodaj wiersz widoczny poniżej. W razie potrzeby zaimportuj android.view.LayoutInflater.

    Metoda onCreateViewHolder() musi zwracać nowy obiekt MarsPropertyViewHolder utworzony przez rozwinięcie widoku GridViewItemBinding i użycie obiektu LayoutInflater z kontekstu ViewGroup rodzica.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. W metodzie onBindViewHolder() usuń komentarz TODO i dodaj wiersze pokazane poniżej. Wywołujesz tu funkcję getItem(), aby uzyskać obiekt MarsProperty powiązany z bieżącą pozycją RecyclerView, a następnie przekazujesz tę właściwość do metody bind() w obiekcie MarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)

Krok 4. Dodaj adapter wiążący i połącz części

Na koniec użyj BindingAdapter, aby zainicjować PhotoGridAdapter listą obiektów MarsProperty. Użycie BindingAdapter do ustawienia danych RecyclerView powoduje, że powiązanie danych automatycznie obserwuje LiveData na liście obiektów MarsProperty. Wtedy adapter powiązania jest wywoływany automatycznie, gdy zmieni się lista MarsProperty.

  1. Otwórz pokój BindingAdapters.kt.
  2. Na końcu pliku dodaj metodę bindRecyclerView(), która przyjmuje jako argumenty obiekt RecyclerView i listę obiektów MarsProperty. Dodaj do tej metody adnotację @BindingAdapter.

    Wpisz androidx.recyclerview.widget.RecyclerViewcom.example.android.marsrealestate.network.MarsProperty, gdy pojawi się prośba.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. W funkcji bindRecyclerView() przekształć recyclerView.adapter na PhotoGridAdapter i wywołaj adapter.submitList() z danymi. Informuje to RecyclerView, kiedy dostępna jest nowa lista.

W razie potrzeby zaimportuj com.example.android.marsrealestate.overview.PhotoGridAdapter.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. Otwórz pokój res/layout/fragment_overview.xml. Dodaj atrybut app:listData do elementu RecyclerView i ustaw go na viewmodel.properties za pomocą powiązania danych.
app:listData="@{viewModel.properties}"
  1. Otwórz pokój overview/OverviewFragment.kt. W onCreateView(), tuż przed wywołaniem funkcji setHasOptionsMenu(), zainicjuj w binding.photosGrid adapter RecyclerView jako nowy obiekt PhotoGridAdapter.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. Uruchom aplikację. Powinna wyświetlić się siatka MarsProperty obrazów. Podczas przewijania w celu wyświetlenia nowych obrazów aplikacja pokazuje ikonę postępu wczytywania, zanim wyświetli sam obraz. Jeśli włączysz tryb samolotowy, obrazy, które nie zostały jeszcze załadowane, będą wyświetlane jako ikony uszkodzonych obrazów.

Gdy nie można pobrać obrazu, aplikacja MarsRealEstate wyświetla ikonę uszkodzonego obrazu. Gdy jednak nie ma sieci, aplikacja wyświetla pusty ekran.

Nie jest to zbyt wygodne dla użytkowników. W tym zadaniu dodasz podstawową obsługę błędów, aby użytkownik mógł lepiej zrozumieć, co się dzieje. Jeśli internet jest niedostępny, aplikacja wyświetli ikonę błędu połączenia. Podczas pobierania listy MarsProperty aplikacja będzie wyświetlać animację wczytywania.

Krok 1. Dodaj stan do modelu widoku

Na początek utwórz LiveData w modelu widoku, aby reprezentować stan żądania internetowego. Należy wziąć pod uwagę 3 stany: wczytywanie, sukces i niepowodzenie. Stan wczytywania występuje, gdy czekasz na dane w wywołaniu funkcji await().

  1. Otwórz pokój overview/OverviewViewModel.kt. U góry pliku (po importach, a przed definicją klasy) dodaj enum, aby reprezentować wszystkie dostępne stany:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Zmień nazwy wewnętrznych i zewnętrznych definicji danych na żywo _response w całej klasie OverviewViewModel na _status. Ponieważ w tej instrukcji dodano obsługę _properties LiveData, pełna odpowiedź usługi internetowej nie została użyta. Aby śledzić bieżący stan, musisz mieć tutaj LiveData, więc możesz po prostu zmienić nazwy istniejących zmiennych.

Zmień też typy z String na MarsApiStatus..

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. Przewiń w dół do metody getMarsRealEstateProperties() i zaktualizuj _response do _status również tutaj. Zmień ciąg znaków "Success" na stan MarsApiStatus.DONE, a ciąg znaków "Failure" na MarsApiStatus.ERROR.
  2. Dodaj stan MarsApiStatus.LOADING na górze bloku try {} przed wywołaniem funkcji await(). Jest to stan początkowy, gdy korutyna jest uruchomiona i czekasz na dane. Pełny blok try/catch {} wygląda teraz tak:
try {
    _status.value = MarsApiStatus.LOADING
   var listResult = getPropertiesDeferred.await()
   _status.value = MarsApiStatus.DONE
   _properties.value = listResult
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
}
  1. Po wystąpieniu stanu błędu w bloku catch {} ustaw _properties LiveData na pustą listę. Spowoduje to wyczyszczenie RecyclerView.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

Krok 2. Dodaj adapter powiązania dla elementu ImageView stanu

W modelu widoku masz teraz stan, ale jest to tylko zestaw stanów. Jak sprawić, aby pojawił się w samej aplikacji? W tym kroku użyjesz ImageView połączonego z powiązaniem danych, aby wyświetlać ikony stanów wczytywania i błędu. Gdy aplikacja jest w stanie ładowania lub błędu, ikona ImageView powinna być widoczna. Po wczytaniu aplikacji ikona ImageView powinna być niewidoczna.

  1. Otwórz pokój BindingAdapters.kt. Dodaj nowy adapter powiązań o nazwie bindStatus(), który przyjmuje argumenty ImageViewMarsApiStatus. W razie potrzeby zaimportuj com.example.android.marsrealestate.overview.MarsApiStatus.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. Dodaj instrukcję when {} w metodzie bindStatus(), aby przełączać się między różnymi stanami.
when (status) {

}
  1. Wewnątrz when {} dodaj przypadek stanu wczytywania (MarsApiStatus.LOADING). W tym stanie ustaw ImageView na widoczny i przypisz do niego animację wczytywania. Jest to ten sam rysunek animacji, którego używasz w Glide w poprzednim zadaniu. W razie potrzeby zaimportuj android.view.View.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Dodaj przypadek dla stanu błędu, czyli MarsApiStatus.ERROR. Podobnie jak w przypadku stanu LOADING ustaw stan ImageView na widoczny i ponownie użyj elementu rysowalnego connection-error.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. Dodaj przypadek dla stanu gotowości, czyli MarsApiStatus.DONE. Odpowiedź jest prawidłowa, więc wyłącz widoczność stanu ImageView, aby go ukryć.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

Krok 3. Dodaj widok ImageView stanu do układu

  1. Otwórz pokój res/layout/fragment_overview.xml. Pod elementem RecyclerView w elemencie ConstraintLayout dodaj element ImageView pokazany poniżej.

    Ten element ImageView ma takie same ograniczenia jak element RecyclerView. Szerokość i wysokość są jednak ustawione na wrap_content, aby wyśrodkować obraz, a nie rozciągnąć go tak, aby wypełniał widok. Zwróć też uwagę na atrybut app:marsApiStatus, który powoduje, że wywołanie widoku BindingAdapter następuje, gdy zmieni się właściwość stanu w modelu widoku.
<ImageView
   android:id="@+id/status_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:marsApiStatus="@{viewModel.status}" />
  1. Włącz tryb samolotowy na emulatorze lub urządzeniu, aby zasymulować brak połączenia z siecią. Skompiluj i uruchom aplikację. Zobaczysz, że pojawi się obraz błędu:

  1. Naciśnij przycisk Wstecz, aby zamknąć aplikację, i wyłącz tryb samolotowy. Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. W zależności od szybkości połączenia sieciowego może się pojawić bardzo krótki wskaźnik ładowania, gdy aplikacja wysyła zapytanie do usługi internetowej, zanim zaczną się ładować obrazy.

Projekt Android Studio: MarsRealEstateGrid

  • Aby uprościć proces zarządzania obrazami, użyj biblioteki Glide do pobierania, buforowania, dekodowania i buforowania obrazów w aplikacji.
  • Aby wczytać obraz z internetu, Glide potrzebuje 2 rzeczy: adresu URL obrazu i obiektu ImageView, w którym ma umieścić obraz. Aby określić te opcje, użyj metod load()into() w Glide.
  • Adaptery powiązań to metody rozszerzające, które znajdują się między widokiem a powiązanymi z nim danymi. Adaptery powiązań zapewniają niestandardowe działanie, gdy dane ulegają zmianie, np. wywołują Glide, aby wczytać obraz z adresu URL do elementu ImageView.
  • Adaptery powiązań to metody rozszerzające oznaczone adnotacją @BindingAdapter.
  • Aby dodać opcje do żądania Glide, użyj metody apply(). Na przykład użyj apply()placeholder(), aby określić rysunek wczytywania, a apply()error(), aby określić rysunek błędu.
  • Aby utworzyć siatkę obrazów, użyj RecyclerViewGridLayoutManager.
  • Aby zaktualizować listę właściwości, gdy ulegnie zmianie, użyj adaptera powiązań między elementem RecyclerView a układem.

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Inne:

W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:

  • W razie potrzeby przypisz pracę domową.
  • Poinformuj uczniów, jak przesyłać projekty.
  • Oceń zadania domowe.

Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.

Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.

Odpowiedz na te pytania

Pytanie 1

Której metody Glide używasz, aby wskazać ImageView, który będzie zawierać załadowany obraz?

▢ into()

▢ with()

▢ imageview()

▢ apply()

Pytanie 2

Jak określić obraz zastępczy, który ma się wyświetlać podczas ładowania przez Glide?

▢ Użyj metody into() z elementem rysowalnym.

▢ Użyj RequestOptions() i wywołaj metodę placeholder() z elementem rysowalnym.

▢ Przypisz właściwość Glide.placeholder do elementu rysowalnego.

▢ Użyj RequestOptions() i wywołaj metodę loadingImage() z elementem rysowalnym.

Pytanie 3

Jak wskazać, że metoda jest adapterem wiążącym?

▢ Wywołaj metodę setBindingAdapter() na obiekcie LiveData.

▢ Umieść metodę w pliku Kotlin o nazwie BindingAdapters.kt.

▢ Użyj atrybutu android:adapter w układzie XML.

▢ Dodaj do metody adnotację @BindingAdapter.

Rozpocznij kolejną lekcję: 8.3. Filtrowanie i widoki szczegółowe z danymi internetowymi

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.