瀏覽代碼

Moved main loading logic to presenter and view

Vadik Sirekanyan 7 年之前
父節點
當前提交
e38ad2212a

+ 33 - 0
app/src/main/java/com/sirekanyan/knigopis/common/BasePresenter.kt

@@ -0,0 +1,33 @@
+package com.sirekanyan.knigopis.common
+
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.disposables.CompositeDisposable
+
+abstract class BasePresenter {
+
+    private val disposables = CompositeDisposable()
+
+    fun <T> Flowable<T>.bind(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit) {
+        disposables.add(subscribe(onSuccess, onError))
+    }
+
+    fun <T> Observable<T>.bind(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit) {
+        disposables.add(subscribe(onSuccess, onError))
+    }
+
+    fun <T> Single<T>.bind(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit) {
+        disposables.add(subscribe(onSuccess, onError))
+    }
+
+    fun Completable.bind(onSuccess: () -> Unit, onError: (Throwable) -> Unit) {
+        disposables.add(subscribe(onSuccess, onError))
+    }
+
+    fun stop() {
+        disposables.clear()
+    }
+
+}

+ 7 - 0
app/src/main/java/com/sirekanyan/knigopis/common/Presenter.kt

@@ -0,0 +1,7 @@
+package com.sirekanyan.knigopis.common
+
+interface Presenter {
+
+    fun stop()
+
+}

+ 19 - 98
app/src/main/java/com/sirekanyan/knigopis/feature/MainActivity.kt

@@ -10,8 +10,6 @@ import android.support.v7.app.AlertDialog
 import android.support.v7.widget.RecyclerView
 import android.support.v7.widget.Toolbar
 import android.view.MenuItem
-import android.view.View
-import android.widget.TextView
 import com.sirekanyan.knigopis.BuildConfig
 import com.sirekanyan.knigopis.R
 import com.sirekanyan.knigopis.Router
@@ -35,18 +33,15 @@ import com.sirekanyan.knigopis.feature.users.UriItem
 import com.sirekanyan.knigopis.feature.users.UsersAdapter
 import com.sirekanyan.knigopis.model.BookDataModel
 import com.sirekanyan.knigopis.model.CurrentTab
-import com.sirekanyan.knigopis.model.CurrentTab.*
+import com.sirekanyan.knigopis.model.CurrentTab.HOME_TAB
+import com.sirekanyan.knigopis.model.CurrentTab.NOTES_TAB
 import com.sirekanyan.knigopis.model.NoteModel
 import com.sirekanyan.knigopis.model.UserModel
 import com.sirekanyan.knigopis.repository.*
 import com.tbruyelle.rxpermissions2.RxPermissions
-import io.reactivex.Flowable
 import kotlinx.android.synthetic.main.activity_main.*
 import kotlinx.android.synthetic.main.books_page.*
-import kotlinx.android.synthetic.main.notes_page.*
-import kotlinx.android.synthetic.main.users_page.*
 import org.koin.android.ext.android.inject
-import retrofit2.HttpException
 
 private const val ULOGIN_REQUEST_CODE = 0
 private const val BOOK_REQUEST_CODE = 1
@@ -65,19 +60,28 @@ class MainActivity : BaseActivity(), Router, MainPresenter.Router {
     private val booksAdapter by lazy { BooksAdapter(::onBookClicked, ::onBookLongClicked) }
     private val usersAdapter by lazy { UsersAdapter(::onUserClicked, ::onUserLongClicked) }
     private val notesAdapter by lazy { NotesAdapter(::onNoteClicked) }
-    private val loadedTabs = mutableSetOf<CurrentTab>()
     private var userLoggedIn = false
     private var booksChanged = false
     private lateinit var loginOption: MenuItem
     private lateinit var profileOption: MenuItem
     private lateinit var currentTab: CurrentTab
+    private lateinit var presenter: MainPresenter
 
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(if (config.isDarkTheme) R.style.DarkAppTheme else R.style.AppTheme)
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         val view = MainViewImpl(getRootView(), booksAdapter, usersAdapter, notesAdapter)
-        MainPresenterImpl(view, this, config).apply { view.callbacks = this }
+        presenter = MainPresenterImpl(
+            view,
+            this,
+            config,
+            bookRepository,
+            userRepository,
+            noteRepository
+        ).apply {
+            view.callbacks = this
+        }
         booksRecyclerView.addItemDecoration(HeaderItemDecoration(StickyHeaderImpl(booksAdapter)))
         val currentTabId = savedInstanceState?.getInt(CURRENT_TAB_KEY)
         val currentTab = currentTabId?.let { CurrentTab.getByItemId(it) }
@@ -129,6 +133,11 @@ class MainActivity : BaseActivity(), Router, MainPresenter.Router {
         }
     }
 
+    override fun onStop() {
+        super.onStop()
+        presenter.stop()
+    }
+
     override fun onSaveInstanceState(outState: Bundle?) {
         super.onSaveInstanceState(outState)
         outState?.putInt(CURRENT_TAB_KEY, currentTab.itemId)
@@ -269,97 +278,9 @@ class MainActivity : BaseActivity(), Router, MainPresenter.Router {
 
     private fun setCurrentTab(tab: CurrentTab, isForce: Boolean = false) {
         currentTab = tab
-        booksPage.show(tab == HOME_TAB)
-        usersPage.show(tab == USERS_TAB)
-        notesPage.show(tab == NOTES_TAB)
-        val isFirst = !loadedTabs.contains(tab)
-        if (isFirst || isForce) {
-            when (tab) {
-                HOME_TAB -> refreshHomeTab(tab)
-                USERS_TAB -> refreshUsersTab(tab)
-                NOTES_TAB -> refreshNotesTab(tab)
-            }
-        }
-    }
-
-    private fun refreshHomeTab(tab: CurrentTab) {
-        bookRepository.observeBooks()
-            .io2main()
-            .showProgressBar()
-            .bind({ books ->
-                booksPlaceholder.show(books.isEmpty())
-                booksErrorPlaceholder.hide()
-                booksAdapter.submitList(books)
-                loadedTabs.add(tab)
-            }, {
-                logError("cannot load books", it)
-                handleError(it, booksPlaceholder, booksErrorPlaceholder, booksAdapter)
-            })
-    }
-
-    private fun refreshUsersTab(tab: CurrentTab) {
-        userRepository.observeUsers()
-            .io2main()
-            .showProgressBar()
-            .bind({ users ->
-                usersPlaceholder.show(users.isEmpty())
-                usersErrorPlaceholder.hide()
-                usersAdapter.submitList(users)
-                loadedTabs.add(tab)
-            }, {
-                logError("cannot load users", it)
-                handleError(it, usersPlaceholder, usersErrorPlaceholder, usersAdapter)
-            })
-    }
-
-    private fun refreshNotesTab(tab: CurrentTab) {
-        noteRepository.observeNotes()
-            .io2main()
-            .showProgressBar()
-            .bind({ notes ->
-                notesPlaceholder.show(notes.isEmpty())
-                notesErrorPlaceholder.hide()
-                notesAdapter.submitList(notes)
-                loadedTabs.add(tab)
-            }, {
-                logError("cannot load notes", it)
-                handleError(it, notesPlaceholder, notesErrorPlaceholder, notesAdapter)
-            })
+        presenter.showPage(tab, isForce)
     }
 
-    private fun <T> Flowable<T>.showProgressBar(): Flowable<T> =
-        doOnSubscribe {
-            if (!swipeRefresh.isRefreshing) {
-                booksProgressBar.show()
-            }
-        }.doOnNext {
-            booksProgressBar.hide()
-        }.doFinally {
-            booksProgressBar.hide()
-            swipeRefresh.isRefreshing = false
-        }
-
-    private fun handleError(
-        th: Throwable,
-        placeholder: View,
-        errPlaceholder: TextView,
-        adapter: RecyclerView.Adapter<*>
-    ) {
-        if (placeholder.isVisible || adapter.itemCount > 0) {
-            toast(th.messageRes)
-        } else {
-            errPlaceholder.setText(th.messageRes)
-            errPlaceholder.show()
-        }
-    }
-
-    private val Throwable.messageRes
-        get() = if (this is HttpException && code() == 401) {
-            R.string.main_error_unauthorized
-        } else {
-            R.string.common_error_network
-        }
-
     private fun onBookClicked(book: BookDataModel) {
         openBookScreen(book)
     }

+ 81 - 3
app/src/main/java/com/sirekanyan/knigopis/feature/MainPresenter.kt

@@ -1,8 +1,20 @@
 package com.sirekanyan.knigopis.feature
 
+import com.sirekanyan.knigopis.common.BasePresenter
+import com.sirekanyan.knigopis.common.Presenter
+import com.sirekanyan.knigopis.common.extensions.io2main
+import com.sirekanyan.knigopis.common.functions.logError
+import com.sirekanyan.knigopis.model.CurrentTab
+import com.sirekanyan.knigopis.model.CurrentTab.*
+import com.sirekanyan.knigopis.repository.BookRepository
 import com.sirekanyan.knigopis.repository.Configuration
+import com.sirekanyan.knigopis.repository.NoteRepository
+import com.sirekanyan.knigopis.repository.UserRepository
+import io.reactivex.Flowable
 
-interface MainPresenter {
+interface MainPresenter : Presenter {
+
+    fun showPage(tab: CurrentTab, isForce: Boolean)
 
     interface Router {
         fun login()
@@ -16,8 +28,25 @@ interface MainPresenter {
 class MainPresenterImpl(
     private val view: MainView,
     private val router: MainPresenter.Router,
-    private val config: Configuration
-) : MainPresenter, MainView.Callbacks {
+    private val config: Configuration,
+    private val bookRepository: BookRepository,
+    private val userRepository: UserRepository,
+    private val noteRepository: NoteRepository
+) : BasePresenter(), MainPresenter, MainView.Callbacks {
+
+    private val loadedTabs = mutableSetOf<CurrentTab>()
+
+    override fun showPage(tab: CurrentTab, isForce: Boolean) {
+        view.showPage(tab)
+        val isFirst = !loadedTabs.contains(tab)
+        if (isFirst || isForce) {
+            when (tab) {
+                HOME_TAB -> refreshHomeTab(tab)
+                USERS_TAB -> refreshUsersTab(tab)
+                NOTES_TAB -> refreshNotesTab(tab)
+            }
+        }
+    }
 
     override fun onLoginOptionClicked() {
         router.login()
@@ -40,4 +69,53 @@ class MainPresenterImpl(
         router.openNewBookScreen()
     }
 
+    private fun refreshHomeTab(tab: CurrentTab) {
+        bookRepository.observeBooks()
+            .io2main()
+            .showProgressBar()
+            .bind({ books ->
+                view.updateBooks(books)
+                loadedTabs.add(tab)
+            }, {
+                logError("cannot load books", it)
+                view.showBooksError(it)
+            })
+    }
+
+    private fun refreshUsersTab(tab: CurrentTab) {
+        userRepository.observeUsers()
+            .io2main()
+            .showProgressBar()
+            .bind({ users ->
+                view.updateUsers(users)
+                loadedTabs.add(tab)
+            }, {
+                logError("cannot load users", it)
+                view.showUsersError(it)
+            })
+    }
+
+    private fun refreshNotesTab(tab: CurrentTab) {
+        noteRepository.observeNotes()
+            .io2main()
+            .showProgressBar()
+            .bind({ notes ->
+                view.updateNotes(notes)
+                loadedTabs.add(tab)
+            }, {
+                logError("cannot load notes", it)
+                view.showNotesError(it)
+            })
+    }
+
+    private fun <T> Flowable<T>.showProgressBar(): Flowable<T> =
+        doOnSubscribe {
+            view.showProgress()
+        }.doOnNext {
+            view.hideProgress()
+        }.doFinally {
+            view.hideProgress()
+            view.hideSwipeRefresh()
+        }
+
 }

+ 96 - 3
app/src/main/java/com/sirekanyan/knigopis/feature/MainView.kt

@@ -2,12 +2,23 @@ package com.sirekanyan.knigopis.feature
 
 import android.content.Context.MODE_PRIVATE
 import android.support.v7.app.AlertDialog
+import android.support.v7.widget.RecyclerView
 import android.view.View
+import android.widget.TextView
 import com.sirekanyan.knigopis.BuildConfig
 import com.sirekanyan.knigopis.R
+import com.sirekanyan.knigopis.common.extensions.hide
+import com.sirekanyan.knigopis.common.extensions.isVisible
+import com.sirekanyan.knigopis.common.extensions.show
+import com.sirekanyan.knigopis.common.extensions.toast
 import com.sirekanyan.knigopis.feature.books.BooksAdapter
 import com.sirekanyan.knigopis.feature.notes.NotesAdapter
 import com.sirekanyan.knigopis.feature.users.UsersAdapter
+import com.sirekanyan.knigopis.model.BookModel
+import com.sirekanyan.knigopis.model.CurrentTab
+import com.sirekanyan.knigopis.model.CurrentTab.*
+import com.sirekanyan.knigopis.model.NoteModel
+import com.sirekanyan.knigopis.model.UserModel
 import com.sirekanyan.knigopis.repository.cache.COMMON_PREFS_NAME
 import kotlinx.android.extensions.LayoutContainer
 import kotlinx.android.synthetic.main.about.view.*
@@ -15,10 +26,21 @@ import kotlinx.android.synthetic.main.activity_main.*
 import kotlinx.android.synthetic.main.books_page.*
 import kotlinx.android.synthetic.main.notes_page.*
 import kotlinx.android.synthetic.main.users_page.*
+import retrofit2.HttpException
 
 interface MainView {
 
     fun showAboutDialog()
+    fun showPage(tab: CurrentTab)
+    fun updateBooks(books: List<BookModel>)
+    fun updateUsers(users: List<UserModel>)
+    fun updateNotes(notes: List<NoteModel>)
+    fun showBooksError(it: Throwable)
+    fun showUsersError(it: Throwable)
+    fun showNotesError(it: Throwable)
+    fun showProgress()
+    fun hideProgress()
+    fun hideSwipeRefresh()
 
     interface Callbacks {
         fun onLoginOptionClicked()
@@ -32,9 +54,9 @@ interface MainView {
 
 class MainViewImpl(
     override val containerView: View,
-    booksAdapter: BooksAdapter,
-    usersAdapter: UsersAdapter,
-    notesAdapter: NotesAdapter
+    private val booksAdapter: BooksAdapter,
+    private val usersAdapter: UsersAdapter,
+    private val notesAdapter: NotesAdapter
 ) : MainView, LayoutContainer {
 
     lateinit var callbacks: MainView.Callbacks
@@ -84,4 +106,75 @@ class MainViewImpl(
         AlertDialog.Builder(context).setView(dialogView).show()
     }
 
+    override fun showPage(tab: CurrentTab) {
+        booksPage.show(tab == HOME_TAB)
+        usersPage.show(tab == USERS_TAB)
+        notesPage.show(tab == NOTES_TAB)
+    }
+
+    override fun updateBooks(books: List<BookModel>) {
+        booksPlaceholder.show(books.isEmpty())
+        booksErrorPlaceholder.hide()
+        booksAdapter.submitList(books)
+    }
+
+    override fun updateUsers(users: List<UserModel>) {
+        usersPlaceholder.show(users.isEmpty())
+        usersErrorPlaceholder.hide()
+        usersAdapter.submitList(users)
+    }
+
+    override fun updateNotes(notes: List<NoteModel>) {
+        notesPlaceholder.show(notes.isEmpty())
+        notesErrorPlaceholder.hide()
+        notesAdapter.submitList(notes)
+    }
+
+    override fun showBooksError(it: Throwable) {
+        handleError(it, booksPlaceholder, booksErrorPlaceholder, booksAdapter)
+    }
+
+    override fun showUsersError(it: Throwable) {
+        handleError(it, usersPlaceholder, usersErrorPlaceholder, usersAdapter)
+    }
+
+    override fun showNotesError(it: Throwable) {
+        handleError(it, notesPlaceholder, notesErrorPlaceholder, notesAdapter)
+    }
+
+    override fun showProgress() {
+        if (!swipeRefresh.isRefreshing) {
+            booksProgressBar.show()
+        }
+    }
+
+    override fun hideProgress() {
+        booksProgressBar.hide()
+    }
+
+    override fun hideSwipeRefresh() {
+        swipeRefresh.isRefreshing = false
+    }
+
+    private fun handleError(
+        throwable: Throwable,
+        placeholder: View,
+        errorPlaceholder: TextView,
+        adapter: RecyclerView.Adapter<*>
+    ) {
+        if (placeholder.isVisible || adapter.itemCount > 0) {
+            context.toast(throwable.messageRes)
+        } else {
+            errorPlaceholder.setText(throwable.messageRes)
+            errorPlaceholder.show()
+        }
+    }
+
+    private val Throwable.messageRes
+        get() = if (this is HttpException && code() == 401) {
+            R.string.main_error_unauthorized
+        } else {
+            R.string.common_error_network
+        }
+
 }