Слайд 1Android Animation:
from hate to love
View Animation
Property Animation
Transitions
Motion Layout
Алексей Зотов
android:toYDelta="200" />
private fun startAnimation(view: View) {
val animation =
AnimationUtils.loadAnimation(this, R.anim.view_animation)
view.startAnimation(animation)
}
Слайд 4Property animator
private fun startAnimation(view: View) {
view.animate()
.translationY(200f)
.setDuration(250)
.start()
}
xmlns:android="http://schemas.android.com/apk/res/android">
-
-
Слайд 5Property animator
private fun startAnimation(view: View) {
view.animate()
.translationY(200f)
.rotation(180f)
.setDuration(400)
.start()
}
Слайд 6private fun startAnimation(view: View) {
view.animate()
.translationY(200f)
.rotation(180f)
.scaleY(3f)
.scaleX(4f)
.setDuration(400)
.start()
}
Property animator
Слайд 7transition
TransitionManager.beginDelayedTransition(main_container)
TransitionManager.go(scene)
private val startSet = ConstraintSet()
private val endSet = ConstraintSet()
startSet.clone(main_container)
endSet.clone(this, R.layout.scene_a)
private
fun startAnimation(reverse: Boolean) {
TransitionManager.beginDelayedTransition(main_container)
(if (reverse) startSet
else endSet).applyTo(main_container)
}
Scene.getSceneForLayout(main_container, R.layout.scene_a, this)
Слайд 8transition
android:transitionName="@string/cat_transition"
private fun openCatActivity(cat: View) {
val bundle = ActivityOptions.makeSceneTransitionAnimation(
this,
cat,
getString(R.string.cat_transition)
).toBundle()
startActivity(
Intent(this, CatInfoActivity::class.java),
bundle
)
}
name="android:windowActivityTransitions">true
-
@transition/cat_open
-
@transition/cat_open
android:id="@+id/motion_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene"
tools:showPaths="true">
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000">
android:id="@+id/motion_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene"
tools:showPaths="true">
Слайд 19Reactive approach:
keep it simple in android
Даниэл Сергеев, AutoRu android
developer
Слайд 20Подходы к написанию приложений
Императивный подход — парадигма программирования, ориентированная на последовательное
выполнение команд, и внешних синхронных операций.
Реактивный подход — парадигма программирования, ориентированная
на потоки данных и асинхронное распространение изменений.
Слайд 21Реактивный подход
Эффективен
В асинхронных приложениях
Для обработки ошибок
Для разгрузки main thread
Недостатки
Высокий порог
вхождения
Высокая сложность
Слайд 22Зачем реактивный подход? Callback hell
Слайд 23Императивный подход
interface IUserManager {
fun getUser(): User
fun
getUserBalance(userId: String): BigDecimal
fun updateUserBalance(userId: String, balance: BigDecimal)
}
private val
manager: IUserManager = UserManager()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val user = manager.getUser()
val balance = manager.getUserBalance(user.id)
val newBalance = balance - payment
manager.updateUserBalance(user.id, newBalance)
view.showSnack("Balance has been updated to $newBalance")
Слайд 24Добавим асинхронность
interface IUserManager {
fun getUser(onSuccess: (User) -> Unit,
onError: (Throwable) -> Unit)
fun getUserBalance(userId: String, onSuccess: (BigDecimal)
-> Unit, onError: (Throwable) -> Unit)
fun updateUserBalance(userId: String, balance: BigDecimal, onSuccess: () -> Unit, onError: (Throwable) -> Unit)
}
private val manager: IUserManager = UserManager()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager.getUser(
onSuccess = { user ->
manager.getUserBalance(
userId = user.id,
onSuccess = { balance ->
val newBalance = balance - payment
manager.updateUserBalance(
userId = user.id,
balance = newBalance,
onSuccess = { view.showSnack("balance updated") },
onError = ::processError
)
},
onError = ::processError
)
},
onError = ::processError
)
}
private fun processError(th: Throwable) { ... }
Слайд 25Добавим асинхронность
interface IUserManager {
fun getUser(onSuccess: (User) -> Unit,
onError: (Throwable) -> Unit)
fun getUserBalance(userId: String, onSuccess: (BigDecimal)
-> Unit, onError: (Throwable) -> Unit)
fun updateUserBalance(userId: String, balance: BigDecimal, onSuccess: () -> Unit, onError: (Throwable) -> Unit)
}
private val manager: IUserManager = UserManager()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager.getUser(
onSuccess = { user ->
manager.getUserBalance(
userId = user.id,
onSuccess = { balance ->
val newBalance = balance - payment
manager.updateUserBalance(
userId = user.id,
balance = newBalance,
onSuccess = { view.showSnack("balance updated") },
onError = ::processError
)
},
onError = ::processError
)
},
onError = ::processError
)
}
private fun processError(th: Throwable) { ... }
Слишком сложно
Слайд 26Реактивный подход
interface IUserManager {
fun getUser(): Single
fun
getUserBalance(userId: String): Single
fun updateUserBalance(userId: String, balance: BigDecimal): Completable
}
private
val manager: IUserManager = UserManager()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager.getUser()
.flatMapCompletable { user ->
manager.getUserBalance(user.id)
.map { balance -> balance - cost }
.flatMap { balance -> updateUserBalance(user.id, balance) }
}
.subscribe( onSuccess = { view.showSnack("balance updated") },
onError = ::processError
)
}
Слайд 27Реактивный подход – это просто? Ну такое…
Слайд 281. RxJava
https://github.com/ReactiveX/RxJava
2. Coroutines
https://kotlinlang.org/docs/reference/coroutines-overview.html
3.
Android LiveData
https://developer.android.com/topic/libraries/architecture/livedata
4. Reactor, Akka, ets..
Реализация реактивного
подхода в android
Слайд 29RxJava is a Java VM implementation of Reactive Extensions: a
library for composing asynchronous and event-based programs by using observable
sequences.
Основные паттерны
Observable
Observer (Subscriber)
Operators
Subscription
Schedulers
rxjava
Слайд 32
https://rxmarbles.com
Rxjava. Операторы на marble диаграммах
Слайд 33Schedulers - особые операторы RxJava, предназначенные для выполнения операций над
Observable на разных потоках
Schedulers.io()
Schedulers.computation()
Schedulers.newThread()
Schedulers.single()
Schedulers.from(Executor executor)
AndroidSchedulers.mainThread()
RxAndroid — библиотека для RxJava, реализующая AndroidScheduler
для выполнения задач на основном потоке андроид приложения
https://github.com/ReactiveX/RxAndroid
Rxjava shedulers. rxandroid
getItemsFromRemoteSource()
.doOnNext { item -> Log.d(TAG, "Emitting item $item on thread ${currentThread().name}") }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { item -> Log.d(TAG, "Consuming item $item on thread ${currentThread().name}") }
.subscribe { item -> showItem(item) }
Слайд 34Проблемы:
Утечка SampleActivity при повороте экрана
Пересоздание презентера при повороте экрана
Rxjava и
жизненный цикл activity/fragment
class SampleActivity: AppCompatActivity() {
private val presenter:
SamplePresenter = SamplePresenter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter.observeModel()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.main())
.subscribe(
{ model -> bindModel(model) },
{ th -> bindError(th) }
)
}
private fun bindModel(model: Model) { ... }
private fun bindError(th: Throwable) { ... }
}
Слайд 35Иньекция презентера и отписка на onStop() решают проблему
Rxjava и
жизненный цикл activity/fragment
class SampleActivity: AppCompatActivity() {
@Inject
lateinit
var presenter: SamplePresenter
private var modelSubscription: Subscription? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState)
ComponentManager.mainComponent.inject(this)
}
override fun onStart() {
super.onStart()
modelSubscription = presenter.observeModel()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.main())
.subscribe(
{ model -> bindModel(model) },
{ th -> bindError(th) }
)
}
override fun onStop() {
super.onStop()
modelSubscription?.unsubscribe()
}
}
Слайд 36RxLifecycle позволяет автоматически завершать rx подписки по событиям жизненного цикла
компонентов андроида
https://github.com/trello/RxLifecycle
Rxjava и жизненный цикл activity/fragment. rxlifecyrcle
class SampleActivity: RxActivity() {
@Inject
lateinit var presenter: SamplePresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ComponentManager.mainComponent.inject(this)
presenter.observeModel()
.subscribeOn(Schedulers.io())
.compose(bindToLifecycle())
.subscribe(
{ userInfo -> bindUserInfo() },
{ th -> bindError() }
)
}
}
Слайд 37Поход в сеть
Маппинг запросов
Горячие подписки на обновление модели
Временные отсчеты (debounce,
таймер)
Ретраи запроса (поллинг)
…
Rxjava. Типичные варианты применения
Слайд 38Retrofit — Type-safe HTTP client for Android and Java by Square,
Inc.
https://square.github.io/retrofit/
Rxjava и Retrofit. Реактивный поход в сеть
interface ScalaApi {
@GET("magazine/articles/snippets")
fun getJournalArticles(
@Query("category") articleCategory: Array
,
@Query("mark") mark: String,
@Query("model") model: String,
@Query("super_gen_id") superGen: String?,
@Query("page_size") pageSize: Int
): Single
@GET("user/offers/{category}")
fun getUserOffers(
@Path("category") category: String = "all",
@Query("page") page: Int,
@Query("sort") sort: String?,
@Query("page_size") pageSize: Int,
@Query("with_daily_counters") withDailyViews: Boolean = false,
@Query("moto_category") motoCategory: List? = null,
@Query("truck_category") truckCategory: List? = null,
@Query("section") state: String? = null,
@Query("status") status: String? = null,
@Query("mark_model") markModel: String? = null,
@Query("price_from") priceFrom: Int? = null
implementation "com.squareup.retrofit2:retrofit:$retrofit"
implementation "com.squareup.retrofit2:converter-gson:$retrofit"
implementation("com.squareup.retrofit2:converter-protobuf:$retrofit") {
transitive = false;
}
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit"
Слайд 39Rxjava. Маппинг запросов
class OfferDetailsInteractor(
private val offersRepository: IOffersRepository,
private val userRepo: IUserOffersRepository,
private val geoRepository: IGeoRepository,
private val recentBadgesRepository: IRecentBadgesRepository
) : IOfferDetailsInteractor {
override fun getOffer(
category: String,
offerId: String,
isUserOffer: Boolean,
rids: List?,
geoRadius: Int?
): Single = Single.zip(
recentBadgesRepository.observeBadges().take(1).toSingle(),
getOffer(isUserOffer, category, offerId, rids, geoRadius),
{ badges, offer -> offer.enrichWithRecentBadges(badges) }
)
.doOnSuccess { cacheOffer(it) }
private fun getOffer(
isUserOffer: Boolean,
category: String,
offerId: String,
rids: List?,
geoRadius: Int?
): Single = when {
isUserOffer -> offersRepository.getUserOffer(category, offerId)
else -> offersRepository.getOffer(category, offerId, rids, geoRadius)
}
}
Слайд 40Rxjava. Горячие подписки на обновление модели
class FavoriteOfferInteractor(...): IFavoriteInteractor {
private val eventsSubj = PublishSubject.create().toSerialized()
override fun switchFavorite(favorite: Offer):
Completable = when {
favoriteRepo.idsCache.contains(favorite.id) -> removeFavorite(favorite)
else -> addFavorite(favorite)
}
override fun favoriteSwitchEvents(): Observable> = eventsSubj
}