Android Kotlin Fundamentals 08.2: wczytywanie i wyświetlanie obrazów z internetu

Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.

Wprowadzenie

Z poprzedniego ćwiczenia z programowania dowiesz się, jak pobierać dane z usługi internetowej i analizować odpowiedź do obiektu danych. Dzięki nim dowiesz się, jak wczytywać i wyświetlać zdjęcia z internetowego adresu URL. Wiesz też, jak utworzyć RecyclerView i używać go do wyświetlania siatki obrazów na stronie przeglądu.

Co warto wiedzieć

  • Jak tworzyć fragmenty i ich używać.
  • Jak używać komponentów architektury, takich jak modele widoku danych, fabryki modeli, przekształcenia i LiveData.
  • Jak pobrać JSON z usługi internetowej REST i przeanalizować te dane w obiektach Kotlin za pomocą bibliotek Retrofit i Moshi.
  • Jak utworzyć układ siatki z użyciem obiektu RecyclerView.
  • Jak działają Adapter, ViewHolder i DiffUtil.

Czego się nauczysz:

  • Korzystanie z biblioteki Glide do ładowania i wyświetlania obrazów z internetowego adresu URL.
  • Dowiedz się, jak używać siatki RecyclerView i adaptera siatki, aby wyświetlać siatkę obrazów.
  • Jak radzić sobie z potencjalnymi błędami podczas pobierania i wyświetlania obrazów.

Co chcesz zrobić

  • Zmodyfikuj aplikację MarsRealEstate, aby uzyskać adres URL obrazu z danych właściwości Marsa, a następnie użyj Glide, aby wczytać i wyświetlić ten obraz.
  • Dodaj do aplikacji animację wczytywania i ikonę błędu.
  • Aby wyświetlić siatkę Marsa, użyj wartości RecyclerView.
  • Dodaj opis i obsługę błędów w elemencie RecyclerView.

W tym ćwiczeniu z programowania (i w powiązanych z nimi ćwiczeniach) będziesz używać aplikacji MarsRealEstate, która wyświetla usługi na Marsie. Aplikacja łączy się z serwerem internetowym, aby pobierać i wyświetlać dane o nieruchomościach, m.in. o ich cenie oraz o tym, czy nieruchomości jest na sprzedaż lub do wynajęcia. Zdjęcia przedstawiające każdą nieruchomość to rzeczywiste zdjęcia Marsa zrobione przez marsjańskie łaziki.

Wersja aplikacji utworzona w tym ćwiczeniu z programowania wypełnia stronę Przegląd, na której wyświetla się siatka obrazów. Obrazy to część danych usługi, które aplikacja otrzymuje z usługi internetowej Marsa. Twoja aplikacja będzie używać biblioteki Glide do wczytywania i wyświetlania obrazów, a RecyclerView – do tworzenia układu siatki dla obrazów. Aplikacja będzie też płynnie obsługiwać błędy sieci.

Wyświetlenie zdjęcia z internetowego adresu może wydawać się proste, ale żeby to zadziałało, trzeba trochę napracować odpowiednie mechanizmy. Obraz musi zostać pobrany, zbuforowany i zdekodowany z formatu skompresowanego do użycia przez Androida. Obraz powinien znajdować się w pamięci podręcznej w pamięci podręcznej lub w obu tych miejscach. Trzeba to robić w wątkach w tle o niskim priorytecie, aby interfejs działał elastycznie. Aby uzyskać lepszą wydajność sieci i CPU, możesz pobrać i odkodować więcej niż jeden obraz naraz. Nauczenie się efektywnego ładowania obrazów z sieci może wymagać jedynie ćwiczeń z programowania.

Na szczęście możesz wykorzystać przygotowaną przez społeczność bibliotekę Glide do pobierania, buforowania, dekodowania i buforowania obrazów. Glide pozostawia dużo mniej pracy niż w przypadku wszystkiego od zera.

Lot wymaga dwóch 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 nieruchomości. Na liście właściwości zwracanych przez usługę internetową jest wyświetlany obraz reprezentujący pierwszą właściwość Marsa. Oto zrzuty ekranu przed i po:

Krok 1. Dodaj zależność Glide

  1. Otwórz aplikację MarsRealEstate z ostatniego ćwiczenia z programowania. (Jeśli nie masz aplikacji, możesz pobrać aplikację MarsRealEstateNetwork tutaj).
  2. Uruchom aplikację, aby sprawdzić, jak działa. Wyświetla szczegółowe informacje o nieruchomości, która jest hipotetycznie dostępna na Marsie.
  3. Otwórz build.gradle (Moduł: aplikacja).
  4. W sekcji dependencies dodaj ten wiersz na potrzeby biblioteki Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"


Zauważ, że numer wersji jest już zdefiniowany oddzielnie w pliku Gradle projektu.

  1. Kliknij Sync Now (Synchronizuj teraz), by ponownie utworzyć projekt z nową zależnością.

Krok 2. Zaktualizuj model widoku

Następnie zaktualizuj klasę OverviewViewModel, aby uwzględnić dane na żywo o jednej usłudze na Marsie.

  1. Otwórz aplikację overview/OverviewViewModel.kt. Tuż pod polem LiveData obiektu _response dodaj wewnętrzne (zmienne) i zewnętrzne (stałe) dane na żywo dla pojedynczego obiektu MarsProperty.

    Jeśli chcesz, możesz zaimportować klasę 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 _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]
}

Cały 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ć z komponentem imgSrcUrl property LiveData:
android:text="@{viewModel.property.imgSrcUrl}"
  1. Uruchom aplikację. TextView wyświetla tylko adres URL obrazu w pierwszej właściwości Marsa. Do tej pory udało Ci się skonfigurować model widoku i dane na żywo dla tego adresu URL.

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

Teraz masz już URL do wyświetlenia obrazu. Teraz możesz zacząć go używać z gestem Glide. W tym kroku używasz adaptera, aby pobrać adres URL z atrybutu XML powiązanego z elementem ImageView, i użyj narzędzia Glide, aby wczytać obraz. Adaptery wiążące to metody rozszerzeń, które znajdują się między widokiem danych a powiązanymi danymi, aby zapewnić niestandardowe działanie w przypadku zmiany danych. W takim przypadku niestandardowym zachowaniem jest wywołanie Glide w celu wczytania obrazu z adresu URL w tagu ImageView.

  1. Otwórz aplikację BindingAdapters.kt. Ten plik będzie zawierać adaptery wiążące, których używasz w całej aplikacji.
  2. Utwórz funkcję bindImage(), która przyjmuje parametry ImageView i String jako parametry. Dodaj adnotację do funkcji @BindingAdapter. Adnotacja @BindingAdapter informuje o wiązaniu danych, że ten adapter wiązania ma zostać użyty, gdy element XML ma atrybut imageUrl.

    Importuj androidx.databinding.BindingAdapter i android.widget.ImageView na żądanie.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. W funkcji bindImage() dodaj blok let {} dla argumentu imgUrl:
imgUrl?.let { 
}
  1. W bloku let {} dodaj poniższy wiersz, aby przekonwertować ciąg adresu URL (z pliku XML) na obiekt Uri. W razie potrzeby zaimportuj androidx.core.net.toUri.

    Ostateczny obiekt Uri powinien 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() jest funkcją rozszerzenia Kotlin z biblioteki podstawowej KTX Androida, więc wygląda na to, że jest częścią klasy String.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. Pozostaw wewnątrz let {}, wywołaj Glide.with(), aby wczytać obraz z obiektu Uri do elementu ImageView. Importuj com.bumptech.glide.Glide na żądanie.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

Krok 4. Zaktualizuj układ i fragmenty

Chociaż Glide wczytała obraz, to jeszcze nic nie jest widoczne. Następnym krokiem jest zaktualizowanie układu i fragmentów za pomocą tagu ImageView, aby wyświetlać obraz.

  1. Otwórz aplikację res/layout/gridview_item.xml. To jest plik zasobu dotyczący układu, który zostanie wykorzystany dla każdego elementu RecyclerView w dalszej części ćwiczenia. Tymczasowo używasz go tylko do wyświetlania pojedynczego obrazu.
  2. Nad elementem <ImageView> dodaj element <data> do 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 wiązania obrazów:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. Otwórz aplikację overview/OverviewFragment.kt. W metodzie onCreateView() skomentuj komentarz, który uzupełnia klasę FragmentOverviewBinding i przypiszesz ją do zmiennej wiązania. Jest to tylko tymczasowa konfiguracja. Wróć tu później.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. Dodaj wiersz, aby zastąpić klasę GridViewItemBinding. Importuj com.example.android.marsrealestate. databinding.GridViewItemBinding, gdy pojawi się taka prośba.
val binding = GridViewItemBinding.inflate(inflater)
  1. Uruchom aplikację. Zobaczysz na liście wyników zdjęcie z pierwszego MarsProperty.

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

Dzięki przesuwaniu można zwiększyć komfort użytkowania. Wyświetla obraz zastępczy podczas ładowania obrazu i wyświetla błąd, jeśli nie uda się go załadować (na przykład jeśli nie ma obrazu lub jest on uszkodzony). W tym kroku dodasz tę funkcję do adaptera wiązania i do układu.

  1. Otwórz aplikację res/drawable/ic_broken_image.xml i kliknij kartę Podgląd po prawej stronie. W przypadku zdjęcia błędu używasz ikony uszkodzonego obrazu, która jest dostępna we wbudowanej bibliotece ikon. Ten wektorowy obiekt rysowalny jest za pomocą atrybutu android:tint, aby kolorować ikonę w kolorze szarym.

  1. Otwórz aplikację res/drawable/loading_animation.xml. Jest to animacja, która jest definiowana za pomocą tagu <animate-rotate>. Animacja obraca obiekt rysowany (loading_img.xml) wokół punktu środkowego. (Animacja nie jest widoczna na podglądzie).

  1. Wróć do pliku BindingAdapters.kt. W metodzie bindImage() zmień wywołanie Glide.with() na wywołanie funkcji apply() między load() a into(). W razie potrzeby zaimportuj com.bumptech.glide.request.RequestOptions.

    Ten kod ustawia obraz zastępczy, który ma być używany podczas wczytywania (element loading_animation, który można rysować). Kod ustawia też obraz do użycia w przypadku niepowodzenia ładowania obrazu (rysunek 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 przez chwilę może się pojawić obraz wczytywania podczas pobierania i wyświetlania gestu usługi. Ikona uszkodzonego obrazu nie będzie jeszcze widoczna, nawet jeśli wyłączysz sieć – naprawisz ją w ostatniej części ćwiczeń z programowania.

Twoja aplikacja wczytuje teraz informacje o usłudze z internetu. Wykorzystując dane z pierwszego elementu listy MarsProperty, udało Ci się utworzyć w widoku danych właściwość LiveData i używasz adresu URL obrazu z tych danych do wypełniania elementu ImageView. Jednak aplikacja ma wyświetlać siatkę obrazów, więc chcesz użyć tagu RecyclerView z elementem GridLayoutManager.

Krok 1. Zaktualizuj model widoku

Obecnie model widoku danych zawiera obiekt _property LiveData, który zawiera 1 obiekt MarsProperty – pierwszy z listy odpowiedzi usługi internetowej. W tym kroku zmienisz wartość LiveData tak, by przechowywała całą listę obiektów MarsProperty.

  1. Otwórz aplikację overview/OverviewViewModel.kt.
  2. Zmień prywatną zmienną _property na _properties. Zmień typ na listę obiektów MarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Zamień zewnętrzne dane transmisji na żywo z property na properties. Dodaj listę także 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 widocznym poniżej. Zmienna listResult zawiera listę obiektów MarsProperty, więc możesz ją tylko przypisać do kategorii _properties.value, zamiast testować prawidłową odpowiedź.
_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ład i fragmenty

Następnym krokiem jest zmiana układu i fragmentów aplikacji tak, aby korzystały z widoku odświeżającego i układu siatki, a nie z widoku pojedynczego obrazu.

  1. Otwórz aplikację res/layout/gridview_item.xml. Zmień wiązanie danych z OverviewViewModel na MarsProperty i zmień nazwę zmiennej na "property".
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. W elemencie <ImageView> zmień atrybut app:imageUrl, aby odnosił się do adresu URL obrazu w obiekcie MarsProperty:
app:imageUrl="@{property.imgSrcUrl}"
  1. Otwórz aplikację overview/OverviewFragment.kt. W onCreateview() usuń komentarz do wiersza, który zwiększa wartość FragmentOverviewBinding. Usuń lub skomentuj linię, która powoduje GridViewBinding. Te zmiany cofają tymczasowe zmiany wprowadzone w ostatnim zadaniu.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. Otwórz aplikację res/layout/fragment_overview.xml. Usuń cały element <TextView>.
  2. Dodaj element <RecyclerView>, który korzysta z układu GridLayoutManager i grid_view_item na potrzeby jednego elementu:
<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 pojedynczy ImageView. W tym kroku powiążesz dane z elementem RecyclerView za pomocą adaptera RecyclerView.

  1. Otwórz aplikację overview/PhotoGridAdapter.kt.
  2. Utwórz klasę PhotoGridAdapter z parametrami konstruktora widocznymi poniżej. Klasa PhotoGridAdapter rozszerza ListAdapter, którego konstruktor wymaga elementu listy, właściciela widoku i implementacji DiffUtil.ItemCallback.

    Jeśli chcesz, możesz importować klasy androidx.recyclerview.widget.ListAdapter i com.example.android.marsrealestate.network.MarsProperty. W kolejnych krokach wdrożysz pozostałe brakujące elementy tego konstruktora, które generują błędy.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. Kliknij dowolne miejsce w klasie PhotoGridAdapter i naciśnij Control+i, by 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 dodaj nowo dodaną definicję obiektu towarzyszącego dla DiffCallback (jak pokazano poniżej).

    Zaimportuj androidx.recyclerview.widget.DiffUtil na żądanie.

    Obiekt DiffCallback rozszerza właściwość DiffUtil.ItemCallback o typ obiektu, który chcesz porównać – MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. Naciśnij Control+i, by zaimplementować metody porównawcze dla tego obiektu: areItemsTheSame() i 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ń zadanie do wykonania. Używaj operatora referencji Kotlin równości (===), która zwraca true, jeśli obiekty odnoszą się do oldItem i newItem.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. W przypadku areContentsTheSame() użyj standardowego operatora równości tylko w identyfikatorach oldItem i newItem.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. Pozostając w klasie PhotoGridAdapter pod obiektem towarzyszącym, dodaj definicję klasy wewnętrznej dla MarsPropertyViewHolder, która powoduje rozszerzenie RecyclerView.ViewHolder.

    Importuj żądania androidx.recyclerview.widget.RecyclerView i com.example.android.marsrealestate.databinding.GridViewItemBinding na żądanie.

    Potrzebujesz elementu GridViewItemBinding, by powiązać MarsProperty z układem, więc przekaż zmienną do MarsPropertyViewHolder. Ponieważ podstawowa klasa ViewHolder wymaga widoku w konstruktorze, przekazujesz do niego wiążący widok główny.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. W MarsPropertyViewHolder utwórz metodę bind(), która przyjmuje obiekt MarsProperty jako argument i ustawia dla niego binding.property. Wywołaj executePendingBindings() po ustawieniu właściwości, co spowoduje natychmiastowe uruchomienie aktualizacji.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. W usłudze onCreateViewHolder() usuń TODO i dodaj wiersz widoczny poniżej. Importuj android.view.LayoutInflater, gdy pojawi się taka prośba.

    Metoda onCreateViewHolder() musi zwracać nowy element MarsPropertyViewHolder, utworzony za pomocą parametru GridViewItemBinding, który jest utworzony z elementu nadrzędnego ViewGroup.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. W metodzie onBindViewHolder() usuń TODO i dodaj wiersze widoczne poniżej. Wywołujesz obiekt getItem(), by pobrać obiekt MarsProperty powiązany z pozycją RecyclerView, a następnie przekażesz tę właściwość do metody bind() w MarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)

Krok 4. Dodaj przejściówkę i połącz części

Następnie użyj BindingAdapter do zainicjowania PhotoGridAdapter z listy obiektów MarsProperty. Użycie danych BindingAdapter do ustawienia danych typu RecyclerView powoduje, że wiązanie danych automatycznie śledzi LiveData na liście obiektów MarsProperty. Następnie adapter wiązania zostanie automatycznie wywołany po zmianie listy MarsProperty.

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

    W razie potrzeby zaimportuj androidx.recyclerview.widget.RecyclerView i com.example.android.marsrealestate.network.MarsProperty.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. W ramach funkcji bindRecyclerView() prześlij recyclerView.adapter do PhotoGridAdapter i wywołaj adapter.submitList() z danymi. Dzięki temu RecyclerView będzie wiedzieć, kiedy dostępna jest nowa lista.

Importuj com.example.android.marsrealestate.overview.PhotoGridAdapter, gdy pojawi się taka prośba.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. Otwórz aplikację res/layout/fragment_overview.xml. Dodaj atrybut app:listData do elementu RecyclerView i ustaw go na wartość viewmodel.properties za pomocą wiązania danych.
app:listData="@{viewModel.properties}"
  1. Otwórz aplikację overview/OverviewFragment.kt. W onCreateView(), tuż przed wywołaniem setHasOptionsMenu(), zainicjuj adapter RecyclerView w tagu binding.photosGrid dla nowego obiektu PhotoGridAdapter.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. Uruchom aplikację. Powinna być widoczna siatka MarsProperty obrazów. Aplikacja wyświetla ikonę postępu ładowania, gdy przewijasz stronę, by zobaczyć nowe obrazy. Jeśli włączysz tryb samolotowy, obrazy, które nie zostały jeszcze wczytane, będą wyświetlane jako ikony uszkodzonego obrazu.

Aplikacja MarsRealEstate wyświetla ikonę uszkodzonego obrazu, gdy nie można pobrać obrazu. Jednak w przypadku braku sieci aplikacja wyświetla pusty ekran.

To nie spodoba się użytkownikom. W tym zadaniu dodasz podstawową obsługę błędów, by użytkownik wiedział, co się dzieje. Jeśli internet nie jest dostę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 danych

Zacznij od utworzenia w widoku danych obiektu LiveData reprezentującego stan żądania internetowego. Dostępne są 3 stany: wczytywanie, powodzenie i niepowodzenie. Stan wczytywania ma miejsce, kiedy czekasz na dane z wywołania kierowanego do źródła await().

  1. Otwórz aplikację overview/OverviewViewModel.kt. W górnej części pliku (po zaimportowaniu, przed definicją klasy) dodaj enum reprezentujący wszystkie dostępne stany:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Zmień nazwy zarówno wewnętrznych, jak i zewnętrznych definicji _response w całej klasie OverviewViewModel na _status. Ponieważ dodano obsługę _properties LiveData wcześniej w tym ćwiczeniu, cała odpowiedź dotycząca usługi internetowej nie została wykorzystana. Aby śledzić bieżący stan usługi, potrzebujesz LiveData. Wystarczy, że zmienisz nazwę 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 zmień także _response na _status. Zmień ciąg znaków "Success" na stan MarsApiStatus.DONE, a ciąg "Failure" na MarsApiStatus.ERROR.
  2. Dodaj stan MarsApiStatus.LOADING u góry bloku adresu try {}, przed wywołaniem await(). To początkowy stan, gdy jest uruchomiona aplikacja i oczekujesz na dane. Cały 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 stanie błędu w bloku catch {} ustaw _properties LiveData na pustą listę. Zostaną usunięte RecyclerView.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

Krok 2. Dodaj adapter wiązania dla stanu ImageView

Teraz masz w modelu widoku stan, ale jest to po prostu zbiór stanów. Jak sprawić, by pojawiała się ona w samej aplikacji? W tym kroku używasz ikony ImageView połączonej z wiązaniem danych, aby wyświetlać ikony stanu wczytywania i błędów. Gdy aplikacja jest w fazie wczytywania lub stanu błędu, ikona ImageView powinna być widoczna. Po zakończeniu wczytywania ImageView powinien być niewidoczny.

  1. Otwórz aplikację BindingAdapters.kt. Dodaj nowy adapter wiązania o nazwie bindStatus(), który przyjmuje wartości ImageView i MarsApiStatus jako argumenty. Importuj com.example.android.marsrealestate.overview.MarsApiStatus, gdy pojawi się taka prośba.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. Aby przełączać się między stanami, dodaj parametr when {} w metodzie bindStatus().
when (status) {

}
  1. W obrębie znaczników when {} dodaj zgłoszenie dotyczące stanu wczytywania (MarsApiStatus.LOADING). W przypadku tego stanu ustaw ImageView jako widoczny i przypisz mu animację wczytywania. Jest to ta sama animacja, która pojawiła się w Glide w poprzednim zadaniu. Importuj android.view.View, gdy pojawi się taka prośba.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Dodaj zgłoszenie o stanie błędu, czyli MarsApiStatus.ERROR. Podobnie jak w przypadku stanu LOADING ustaw stan ImageView na widoczny i ponownie użyj rysowanego błędu połączenia.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. Dodaj zgłoszenie o stanie „Gotowe”: MarsApiStatus.DONE. Tutaj wyświetla się odpowiednia odpowiedź. Wyłącz widoczność stanu ImageView, by go ukryć.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

Krok 3. Dodaj stan ImageView do układu

  1. Otwórz aplikację res/layout/fragment_overview.xml. Poniżej elementu RecyclerView w obiekcie ConstraintLayout dodaj ImageView pokazany poniżej.

    Ten ImageView ma te same ograniczenia co RecyclerView. Szerokość i wysokość są jednak mierzone wrap_content, aby wyśrodkować obraz, a nie rozciągnąć go, aby wypełnić cały widok. Zwróć uwagę na atrybut app:marsApiStatus, który zawiera widok wywołujący BindingAdapter w przypadku zmiany właściwości 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 w emulatorze lub urządzeniu, aby zasymulować brak połączenia sieciowego. Skompiluj i uruchom aplikację, a zobaczysz komunikat o błędzie:

  1. Kliknij przycisk Wstecz, by zamknąć aplikację i wyłączyć tryb samolotowy. Wróć do ekranu Ostatnie.

Projekt na Android Studio: MarsRealEstateGrid

  • Aby uprościć proces zarządzania obrazami, użyj biblioteki Gide do pobrania, zbuforowania, dekodowania i buforowania obrazów w aplikacji.
  • Aby wczytać obraz z internetu, Glide potrzebuje dwóch rzeczy: adresu URL obrazu oraz obiektu ImageView, w którym zostanie umieszczony obraz. Aby określić te opcje, użyj metod load() i into() w aplikacji Glide.
  • Adaptery wiążące to metody rozszerzeń, które znajdują się między widokiem danych a powiązanymi z nim danymi. Adaptery wiążące zapewniają niestandardowe działanie w przypadku zmiany danych, na przykład wywołanie Glide w celu wczytania obrazu z adresu URL do elementu ImageView.
  • Adaptery wiązania to metody rozszerzeń z adnotacją @BindingAdapter.
  • Aby dodać opcje do żądania gestami, użyj metody apply(). Na przykład użyj apply() z placeholder(), aby określić rysowanie rysujące, i apply() z error(), aby wskazać błąd rysowalny.
  • Aby utworzyć siatkę obrazów, użyj RecyclerView z GridLayoutManager.
  • Aby zaktualizować listę właściwości po zmianie, użyj adaptera wiązania między elementem RecyclerView i układem.

Kurs Udacity:

Dokumentacja dla programistów Androida:

Inne:

Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:

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

Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.

Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.

Odpowiedz na te pytania

Pytanie 1

Jakiej metody gestu używasz, by wskazać właściwość ImageView, która będzie zawierać wczytany obraz?

into()

with()

imageview()

apply()

Pytanie 2

Jak określić obraz zastępczy, który ma wyświetlać się podczas przesuwania?

▢ Skorzystaj z metody into() z rysunkiem.

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

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

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

Pytanie 3

Jak wskazujecie, że metoda jest adapterem wiążącym?

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

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

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

▢ Dodaj metodę @BindingAdapter.

Rozpocznij następną lekcję: 8.3 Filtrowanie i widoki szczegółów z danymi z internetu

Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.