Wątek przeniesiony 2019-07-19 10:56 z przez Patryk27.

Jakie technologie do napisania aplikacji na Androida?

Odpowiedz Nowy wątek
2019-07-19 10:21
0

Hej, nie klepałem nic na Androida przez 2 lata.

Ktoś aktualny w temacie mógłby wypowiedzieć się na temat obecnie używanych architektur w apkach ? Wcześniej stosowałem zwyczajne MVP i było ok. Jak to jest teraz ? Kumpel w robocie stosuje MVVM i jakieś liby do bindowania zależności, Daggery itd. Nie chciałbym za dużo czasu poświęcać na naukę nowych technologii na Andka, chcę to po prostu jak najszybciej w miarę sensownie naklepać. Apka to ma być 4-5 prostych ekranów. Proste use casy, zwykłe uderzenia do resta bez większej logiki, logowanie przez fb/google/customowo.

Widoki się robi nadal w xmlu z łapy czy jakieś lepsze podejście ?

W sumie to chciałbym też wypuścić to na iOS .. może lepiej react ?

Pozdro

edytowany 3x, ostatnio: Adam Boduch, 2019-07-19 11:02
Dagger, to już był w obiegu sporo ponad 2 lata temu i jest to najbardziej popularna biblioteka do DI na Androida, tylko API się zmieniało i Google go w międzyczasie przejął, więc trzeba sobie dokumentację przejrzeć. Do DI Guica używało się chyba z 5 lat temu albo wcześniej. Nie korzystałeś w ogóle z DI? :P - wiciu 2019-07-30 14:42

Pozostało 580 znaków

2019-09-02 12:37
0
V-2 napisał(a):

Metoda onRetainNonConfigurationInstance jest deprecated od niepamiętnych czasów (API 13 czy 15).

Nie, nie jest.

onRetainCustomNonConfigurationInstance (a nie "custom last") jest deprecated od listopada, z zaleceniem używania ViewModels:

onRetainCustomNonConfigurationInstance has been deprecated. Use a ViewModel for storing objects that need to survive configuration changes.

źródło

Racja, jakbym był zmuszony korzystać z ComponentActivity, to faktycznie korzystałbym z metody, która jest deprecated. Dopiero jakby ją usunęli albo zrobili final, to zrobiłbym dodatkowy krok i dodał jeden dodatkowy ViewModel do przechowywania mojej konfiguracji.

Taka jest oficjalnie zalecana praktyka. Nie ma powodu iść pod prąd i wyważać drzwi otwartych, reimplementując rozwiązania z SDK.

Fragment, Loader i AsyncTask też były/są oficjalnymi zalecanymi praktykami. I też nie widzę sensu reimplementacji istniejących rozwiązań. Wolę pisać lepsze.

Architektura jest dobrze znana każdemu .NET-owcowi. ViewModel ma modelować widok, i tyle. Architecture Components pozwala tak pisać. Jeśli ktoś pakuje do viewmodelu logikę (zamiast wyekstrahować ją gdzie indziej), libka oczywiście nie będzie w stanie go przed tym powstrzymać, ale winowajcą takiego zamieszania będzie ów programista, a nie Google.

Ale to Google sam daje takie przykłady (i zarządzanie samą klasą), gdzie ViewModel ma logikę, dostęp do DAO, repozytoriów, serwisów itd. U nich ViewModel to nie jest model danych, który ma zostać zaprezentowany przez widok, tylko kontroler w warstwie prezentacji.

edytowany 4x, ostatnio: Michał Sikora, 2019-09-02 12:54

Pozostało 580 znaków

2019-09-02 12:57
V-2
0
Michał Sikora napisał(a):
V-2 napisał(a):

Metoda onRetainNonConfigurationInstance jest deprecated od niepamiętnych czasów (API 13 czy 15).

Nie, nie jest.

Tu rzeczywiście, od-deprekowali ją. Choć historia tych zmian była dość burzliwa: https://www.reddit.com/r/andr[...]n_to_me_why_aac_is_trying_to/

Fragment, Loader i AsyncTask też były/są oficjalnymi zalecanymi praktykami. I też nie widzę sensu reimplementacji istniejących rozwiązań. Wolę pisać lepsze.

Każdy lubi być architektem i pisać lepsze :) A potem my, biedne szaraki, zostajemy w projekcie z Własną Biblioteką Michała. Którego nie da się już o nic zapytać. Ani wyguglować w necie odpowiedzi, gdy pojawi się jakiś problem z "lepszym rozwiązaniem". Bo świat zewnętrzny na oczy go nie widział.

Nie mówię teoretycznie, pracowałem z takimi ludźmi... Zamiast Retrofita zastawałem w projekcie Bibliotekę Sebastiana (skądinąd bardzo zdolnego, poszedł później pracować w jednym z gigantów - choć podejrzewam, że tam nie dostał już tyle twórczej swobody).

Fragmenty - przy wszystkich ich niedociągnięciach i słabościach - są jednak używane z powodzeniem do dziś. A gdybym chciał sięgnąć po alternatywę, to bym ograniczył się do użycia innych konstruktów natywnych (np. Activity i własne layouty). Ewentualnie sięgnął po jakieś rozwiązanie, które rzeczywiście funkcjonuje w społeczności: coś w stylu Flow & Mortar czy Conductor. A i to niechętnie. Jeśli masz lepsze rozwiązanie niż ViewModels, to - jak to się mówi - "put your money where your mouth is", i wrzuć na GitHuba jako bibliotekę. Jeśli zaoferuje konkretne przewagi nad oficjalnym (a nie tylko będące kwestią prywatnego gustu), zostanie podchwycone. Natomiast pisanie sobie własnego rozwiązania w projektowym zaciszu, to jest na dłuższą metę najgorszy możliwy wybór.

Ale to Google sam daje takie przykłady, gdzie ViewModel ma logikę, dostęp do DAO, repozytoriów, serwisów itd. U nich ViewModel to nie jest model danych, który ma zostać zaprezentowany przez widok, tylko kontroler w warstwie prezentacji.

Nieprawda. W szkieletowych przykładach może tak to wygląda, dla uproszczenia. Wystarczy jednak poczytać oficjalną dokumentację.

Nie szukając daleko, na dzień dobry na stronie https://developer.android.com[...]raries/architecture/viewmodel masz podlinkowany artykuł ViewModels and LiveData: Patterns + AntiPatterns, który omawia antywzorzec "Fat ViewModels", zalecając "moving some logic out to a presenter [...] adding a Domain layer", itd.

Jeśli ktoś powie ci, że przygotowuje lepsze rozwiązanie niż X - a z rozmowy wynika, że nie przeczytał głównej strony dokumentacji do X, lub w każdym razie pisałby takie rzeczy jak ktoś, kto by jej nie przeczytał - to jaki byś powziął na ten temat pogląd? :) Serio pytam


Nie ma najmniejszego powodu, aby w CV pisać "email" przed swoim adresem mailowym, "imię i nazwisko" przed imieniem i nazwiskiem" ani "zdjęcie mojej głowy od przedniej strony" obok ewentualnego zdjęcia.
edytowany 4x, ostatnio: V-2, 2019-09-02 13:25

Pozostało 580 znaków

2019-09-02 15:37
1
V-2 napisał(a):

Każdy lubi być architektem i pisać lepsze :) A potem my, biedne szaraki, zostajemy w projekcie z Własną Biblioteką Michała. Którego nie da się już o nic zapytać. Ani wyguglować w necie odpowiedzi, gdy pojawi się jakiś problem z "lepszym rozwiązaniem". Bo świat zewnętrzny na oczy go nie widział.

Gdzie ja napisałem, że w komercyjnym projekcie nie używam powszechnych rozwiązań? Bo zaznaczyłem coś przeciwnego. Że właśnie ze względu na łatwość i dostępność używam tych ze standardowych bibliotek, żeby nie mieszać za dużo.

Jeśli masz lepsze rozwiązanie niż ViewModels, to - jak to się mówi - "put your money where your mouth is", i wrzuć na GitHuba jako bibliotekę.

W przypadku ViewModeli nie trzeba żadnej biblioteki. Ale rozumiem, że nie to jest celem pytania i powiedzmy, że zamienimy w tym wypadku na bibliotekę zastępującą fragmenty. Może i mógłbym. Pewnie by wyszło tak sobie ze względu na takie czynniki jak umiejętności, przypadki brzegowe, ilość czasu, ilość osób pracujących nad biblioteką i ją testujących. Nie wiem czego to ma dowodzić. W projektach niekomercyjnych, własnych albo krótkich (gdzie byłem sam) zawsze korzystałem z alternatywnych bibliotek, które mnie nie irytowały, albo pisałem własne rozwiązania.

Jeśli zaoferuje konkretne przewagi nad oficjalnym (a nie tylko będące kwestią prywatnego gustu), zostanie podchwycone.

To akurat, już słowem dygresji, nie wydaje mi się, żeby było prawdą.

Nieprawda. W szkieletowych przykładach może tak to wygląda, dla uproszczenia. Wystarczy jednak poczytać oficjalną dokumentację.

Nie szukając daleko, na dzień dobry na stronie https://developer.android.com[...]raries/architecture/viewmodel masz podlinkowany artykuł ViewModels and LiveData: Patterns + AntiPatterns, który omawia antywzorzec "Fat ViewModels", zalecając "moving some logic out to a presenter [...] adding a Domain layer", itd.

Google sam ułatwia złe rozwiązania. Dodanie cyklu życia do klasy, ułatwiony dostęp do zasobów systemowych w postaci AndroidViewModel, rozszerzenia w postaci modułu SavedStateHandle. Ja już pomijam patologie, że ktoś czyta bezpośrednio z bazy danych w ViewModelu. Przykłady w praktyce:

Jedyna aplikacja, która robi rzeczy rozsądnie w ramach tej biblioteki to Tivi. W pozostałych niektóre ViewModele wyglądają ok, bo mało w ogóle mogą robić. Niektóre zależą od frameworka. Niektóre robią milion rzeczy od dużej ilości logiki po zarządzanie wątkami.

Jeśli ktoś powie ci, że przygotowuje lepsze rozwiązanie niż X - a z rozmowy wynika, że nie przeczytał głównej strony dokumentacji do X, lub w każdym razie pisałby takie rzeczy jak ktoś, kto by jej nie przeczytał - to jaki byś powziął na ten temat pogląd? :) Serio pytam

Nie wiem czy chcesz mi tendencyjnie zarzucić, że piszę jakbym nie czytał dokumentacji. Ale odpowiadając - pewnie miałbym wątpliwości, co do takiej osoby.

edytowany 1x, ostatnio: Michał Sikora, 2019-09-02 16:04
Nawiązując do przykładów to klasycznie w teorii wszyscy robią zgodnie z wzorcami, testują, zawsze są na bieżąco a w życiu i praktyce już jest weryfikacja ;) - dbCooper 2019-09-03 09:35

Pozostało 580 znaków

2019-09-04 08:29
0

Dziękuję serdecznie za odpowiedzi. Udało mi się mniej więcej wszystko ze sobą ograć. Zastanawia mnie jedynie połączenie korutyn i live daty.

Bo architekturę mam mniej więcej taką:

LiveModele są "głupie" i zawierają tylko model widoku. Do nich wstrzykuję jakieś Serwisy udostępniające dane i wykonujące logikę. Do Serwisów tych wstrzykuję po prostu Api retrofita.

I teraz tak, dla przykładu załóżmy, że omawiamy pobieranie danych Usera.

Retrofit zatem ma taką metodę:

suspend fun getUser(): User

W serwisie opakuję to w try catch i wrapuję w jakiś ServiceReponse<t>, który dodatkowo zawiera info o błędzie.

Pytanie teraz czy Serwisy mają zwracać LiveData<ServiceResponse<user>>> czy suspend <ServiceResponse<user>> czy jeszcze co innego ?

Gdzieś muszę się pozbyć tego suspend ?

Pozostało 580 znaków

2019-09-04 20:33
0

suspend ostatecznie używasz w jakimś zakresie korutyn. Warto obejrzeć tę prezentację od Romana. Plus uzupełnienie, bo w prezentacji jest błąd - https://medium.com/@elizarov/[...]-hierarchical-csp-e5910d137cc.

Co do kodu, to koncepcyjnie mogłoby to tak wyglądać. Lepiej by było opakować te kilka LiveData w jedną, ale już mi się nie chciało na potrzeby przykładu.

class AuthActivity : AppCompatActivity() {
  override fun onCreate(inState: Bundle?) {
    super.onCreate(savedInstanceState)
    val viewModel = ViewModelProviders
      .of(this, AuthViewModelFactory)
      .get(AuthViewModel::class.java)
    setContentView(R.layout.activity_main)

    val emailField = findViewById<EditText>(R.id.email)
    val passwordField = findViewById<EditText>(R.id.password)
    val error = findViewById<TextView>(R.id.error)
    val signInButton = findViewById<Button>(R.id.signInButton)

    viewModel.isSigningIn.observe(this, Observer { isSigning ->
      signInButton.isEnabled = !isSigning
    })

    viewModel.authError.observe(this, Observer { error.text = it })

    viewModel.user.observe(this, Observer { user ->
      if (user != null) TODO("Navigate to another screen")
    })

    signInButton.setOnClickListener {
      val email = "${emailField.text}"
      val password = "${passwordField.text}"
      viewModel.signIn(SignInRequest(email, password))
    }
  }
}

sealed class Result<T>
data class Failure<T>(val reason: String) : Result<T>()
data class Success<T>(val value: T) : Result<T>()

data class User(val name: String, val age: Int)

data class SignInRequest(val email: String, val password: String) {
  fun hasInvalidCredentials(): Boolean = email.isBlank() || password.isBlank()
}

interface AuthService {
  suspend fun signIn(request: SignInRequest): Result<User>
}

class AuthViewModel(private val authService: AuthService) : ViewModel(), CoroutineScope {
  private val job = Job()
  override val coroutineContext get() = job

  private val _isSigningIn = MutableLiveData<Boolean>()
  val isSigningIn: LiveData<Boolean> get() = _isSigningIn

  private val _authError = MutableLiveData<String?>()
  val authError: LiveData<String?> get() = _authError

  private val _user = MutableLiveData<User?>()
  val user: LiveData<User?> get() = _user

  fun signIn(request: SignInRequest) {
    _authError.postValue(null)

    val invalidCredentials = request.hasInvalidCredentials()
    if (invalidCredentials) {
      _authError.postValue("Email or password cannot be blank!")
      return
    }

    launch(context = coroutineContext) {
      _isSigningIn.postValue(true)
      val response = authService.signIn(request)
      _isSigningIn.postValue(false)

      when (response) {
        is Failure -> _authError.postValue(response.reason)
        is Success -> _user.postValue(response.value)
      }
    }
  }

  override fun onCleared() = job.cancel()
}
edytowany 4x, ostatnio: Michał Sikora, 2019-09-05 09:15
Pokaż pozostałe 3 komentarze
Znajomość Javy na pewno jest pomocna. Ciężko mi powiedzieć, czy jest konieczna, ale myślę, że tak. Jeszcze zależy od charakteru pracy. Gdybym rekrutował kogoś na początkujące stanowisko i by znał dobrze tylko Kotlina a z Javy był noga, to nie stanowiłoby to dla mnie problemu, żeby taką osobę zatrudnić. Tylko wydaje mi się, że większość osób by tak nie miała i Java jest jeszcze wymagana. - Michał Sikora 2019-09-15 16:38
To jesteś wyjątkiem i stawiasz na nowe technologie. - prototype 2019-09-15 17:42
Nie nazwałbym Kotlina nową technologią. Używam go na Androidzie od blisko 4 lat. - Michał Sikora 2019-09-15 17:43
Programowanie w Dart wydaje się być dużo prostsze od Javy i Kotlina, do tego emulator GenyMotion. https://www.youtube.com/watch?v=5Goq8DoVc5k - prototype 2019-09-16 21:45
Co jest prostszego w Darcie? Zwłaszcza w stosunku do Kotlina. Zgodziłbym się, że Dart ma więcej rzeczy niż Java. Ale nie wiem pod jakim względem jest prostszy. Jest moim zdaniem nawet trudniejszy, bo dużo łatwiej o popełnienie błędów, których nie wychwyci kompilator. A GenyMotion nie ma nic wspólnego z językiem. Możesz z GenyMotion korzystać z czymkolwiek, co jest w stanie zbudować aplikację apk. - Michał Sikora 2019-09-16 22:21

Pozostało 580 znaków

2019-09-04 20:57
0

ViewModel to jest słowo z wieloma znaczeniami:

  1. ViewModel w MVVM jako komponent sterujący widokiem
  2. ViewModel w Architecture Components
  3. ViewModel w podejściu z bindingami w xmlu gdzie trzyma tylko dane

Warto się upewnić o którym znaczeniu słowa się rozmawia. Google na pewno nie jest wzorcem do naśladowania, bez opensourcowych bibliotek do Androida byłaby bieda w programowaniu aplikacji na ten system.

edytowany 1x, ostatnio: viader, 2019-09-04 20:57

Pozostało 580 znaków

2019-09-14 13:25
0

Dzięki Waszej pomocy zacząłęm w wolnych chwilach ruszać temat do przodu .. trochę mało czasu ostatnio, ale no staram się. Po tych wszystkich tutkach i Waszych odpowiedziach naszły mnie takie pytania.

Czym się różni takie rozwiązanie:

fun login(loginForm: LoginForm): LiveData<ServiceResult<AuthToken>> {
        val liveData = MutableLiveData<ServiceResult<AuthToken>>()

        CoroutineScope(Dispatchers.IO).launch {
            val test = safeCall { api.login(loginForm) }
            liveData.postValue(test)
        }

        return liveData
}

Od takiego:

2.

private val _authToken = MutableLiveData<ServiceResult<AuthToken>?>()
val authToken: LiveData<ServiceResult<AuthToken>?> = _authToken

fun login(loginForm: LoginForm) {

        CoroutineScope(Dispatchers.IO).launch {
            val test = safeCall { api.login(loginForm) }
            _authToken.postValue(test)
        }
}

Czy poza po prostu sposobem implementacji ma to jakieś konswekencje większe ? W 1 przypadku podpinam obserwator pod metodę, w 2 obserwuję zmienną.

Kolejna rzecz, do której nie wiem jak podejść:
Korzystam z Glide'a do ładowania obrazków do ImageView. Chciałbym zrobić coś takiego, że ładuję z API listę 10 zdjęć (urli) i chciałbym, żeby to działało tak, że wyświetlam pierwszy i po kliknięciu na jakiś button od razu się wyświetla drugi w tym miejscu (żeby nie trzeba było czekać na załadowanie kolejnego tylko żeby był on już załadowany pod spodem). Myślałem, żeby zrobić jakiś własny view i nałożyć na siebie 10 image view, ale nie wiem czy to dobry pomysł ?

edytowany 3x, ostatnio: Bambo, 2019-09-14 13:26

Pozostało 580 znaków

2019-09-14 14:23
2

To pierwsze rozwiązanie wydaje mi się mocno niepraktyczne. LiveData to po prostu strumień danych. Taki bardzo ubogi Flux/Observable, który zawsze wypycha dane na głównym wątku. Może pokaż jak wyglądałoby użycie tej pierwszej funkcji.

Co do Glidea to nie korzystam, więc nie wiem, ale powinien mieć jakiś mechanizm, żeby załadować obrazki do cache'a przed ich wyświetleniem. Większość bibliotek do ładowania obrazków na to pozwala. Jeśli nie ma czegoś takiego, to możesz też skorzystać np. z ViewPagera ustawić mu offscreenPageLimit na ile tam potrzebujesz (wtedy Glide załaduje odpowiednio więcej obrazków) i dodać własną obsługę kliknięć itd.

edytowany 2x, ostatnio: Michał Sikora, 2019-09-14 14:24

Pozostało 580 znaków

2019-09-14 14:55
0

Ok, to drugie rozwiązanie jest lepsze. Tylko teraz trochę załapałem mind fucka z odpowiedzialnością poszczególnych warstw. Bo tak: z jednej strony mamy ViewModele, które zwracają live daty, na które się subskrybujuemy. Z drugiej strony mamy Retrofita zwracającego Response<t>. Teoretycznie do ViewModelu mogę wrzucić całą logikę i ok. No ale czytałem, że w ViewModelach nie powinno być logiki. Zrobiłem zatem jeszcze jedną wartstwę serwisów, do których wstrzykuję api retrofita, tam ogarniam logikę całą i zwracam LiveDaty. No ale w takim razie wychodzi na to, że ViewModele są tylko przelotką i nic nie robią :O Nie wiem co robię nie tak. Do tego widziałem, gdzieś jeszcze, że używa się Transformations::map i ::switchMap. To dobra praktyka ? Może moja warstwa serwisów nie powinna zwracać LiveDaty tylko po prostu jakieś resulty Succes<t>, Error<t>, które zostałyby ograne w ViewModelach ?

edytowany 1x, ostatnio: Bambo, 2019-09-14 14:55

Pozostało 580 znaków

2019-09-14 15:12
1

No ja bym nie zwracał z serwisów LiveData tylko zawieszającą funkcję i z jej użyciem mapował itd. Te wszystkie Transformations są kiepskie moim zdaniem, bo się wykonują na głównym wątku. W ogóle LiveData jest dla mnie dosyć średnią konstrukcją.

Głównymi zadaniami ViewModeli jest przechowywanie danych dla widoku i długotrwających operacji na czas zmiany konfiguracji aplikacji, żeby nie dochodziło do wycieków pamięci. Na moje to właśnie tam powinny żyć LiveData, ale może ktoś bardziej doświadczony z tą klasą ma lepsze podejście, bo piszę to bardziej na podstawie własnych przemyśleń i doświadczeń ludzi z pracy czy jakichś projektów na GH.

edytowany 1x, ostatnio: Michał Sikora, 2019-09-14 15:12

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

2 użytkowników online, w tym zalogowanych: 0, gości: 0, botów: 2

Robot: CCBot (2x)