|
|
The Akamu Development for Android (Kotlin) follows the guidelines of the Model View Presenter Design Pattern. The basics of MVP can be found e.g. on this page: [MVP for Android](https://antonioleiva.com/mvp-android/)
|
|
|
|
|
|
This Wiki page should help to get an overivew of how the MVP pattern is embeded in our application.
|
|
|
|
|
|
****Contract****
|
|
|
|
|
|
When implementing a new activity e.g. the TitleActivity where you have the opportunity to change your title we start by providing an interface, the contract. It contains two further interfaces: one for the view and one for the presenter.
|
|
|
|
|
|
```kotlin
|
|
|
interface TitleContract {
|
|
|
/** interface containing functions of our activity **/
|
|
|
interface View {
|
|
|
}
|
|
|
/** interface containing functions of our presenter **/
|
|
|
interface Presenter {
|
|
|
}
|
|
|
|
|
|
}
|
|
|
```
|
|
|
In these interfaces we declare the functions used by the view and the presenter respectively. In our example we want to let the user know if setting a new title in the server was successful or not. So we declare the functions `showUpdateTitleSuccess ` and `showUpdateTitleFailed` in the view interface:
|
|
|
|
|
|
```kotlin
|
|
|
interface View {
|
|
|
/** Let the user know that the title was updated**/
|
|
|
fun showUpdateTitleSuccess()
|
|
|
/** Let the user know that it was not possible to update the title **/
|
|
|
fun showUpdateTitleFailed()
|
|
|
}
|
|
|
```
|
|
|
The presenter should do the logic or pass data via an interactor to the model. Therefore we declare the function `updateUserTitle`
|
|
|
|
|
|
```kotlin
|
|
|
interface Presenter {
|
|
|
/** Tries to update the user title with a new title **/
|
|
|
fun updateUserTitle(titleAdapter: TitleAdapter, currentTitle: Title, clickedTitle: Title)
|
|
|
}
|
|
|
```
|
|
|
|
|
|
****View****
|
|
|
|
|
|
Our activity represents the view and therefore implements the view interface of the contract. It should be as dumb and simple as possible and should do no logic. We also instantiate the corresponding presenter which needs an instance of an interactor. This will be explained later on this page.
|
|
|
|
|
|
```kotlin
|
|
|
class TitleActivity :
|
|
|
AppCompatActivity(),
|
|
|
TitleContract.View
|
|
|
TitleAdapter.TitleOnClickHandler {
|
|
|
|
|
|
// Make an instance of the corresponding presenter for this view
|
|
|
private var presenter: TitlePresenter = TitlePresenter(TitleInteractor(TitleDataSourceImpl()), this)
|
|
|
|
|
|
private var user: User? = null
|
|
|
|
|
|
private var titleAdapter: TitleAdapter? = null
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
super.onCreate(savedInstanceState)
|
|
|
setContentView(R.layout.activity_title)
|
|
|
|
|
|
// Get the local app user by passing the activity's context
|
|
|
user = AppUserUtil.getLocalAppUser(this)
|
|
|
|
|
|
// The titles are represented in a list. For this list we need an adapter
|
|
|
titleAdapter = TitleAdapter(this)
|
|
|
// Connect the recycler view with this adapter
|
|
|
recyclerView_title.adapter = titleAdapter
|
|
|
}
|
|
|
|
|
|
override fun onClick(title: Title) {
|
|
|
presenter.updateUserTitle(titleAdapter!!, user!!.title, title)
|
|
|
}
|
|
|
|
|
|
override fun showUpdateTitleSuccess() {
|
|
|
val successful = Snackbar.make(findViewById(R.id.Coordinator_choose_title),
|
|
|
R.string.title_updated,
|
|
|
Snackbar.LENGTH_LONG)
|
|
|
successful.show()
|
|
|
}
|
|
|
|
|
|
override fun showUpdateTitleFailed() {
|
|
|
val unsuccessful = Snackbar.make(findViewById(R.id.Coordinator_choose_title),
|
|
|
R.string.could_not_update_title,
|
|
|
Snackbar.LENGTH_LONG)
|
|
|
unsuccessful.show()
|
|
|
}
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
****Presenter****
|
|
|
|
|
|
The presenter class for our TitleActivity implements the presenter interface and passes the data/name of the new title via an interactor to the model.
|
|
|
|
|
|
```kotlin
|
|
|
// Needs an interactor for passing the data to the model
|
|
|
class TitlePresenter(private val interactor: TitleInteractor, private val view: TitleContract.View) :
|
|
|
TitleContract.Presenter, BasePresenter<TitleContract.View>() {
|
|
|
|
|
|
override fun updateUserTitle(titleAdapter: TitleAdapter, currentTitle: Title, clickedTitle: Title) {
|
|
|
// some logic: only do an update call to the server if the titles differ
|
|
|
if(currentTitle != clickedTitle) {
|
|
|
interactor.updateUserTitle(clickedTitle, object : TitleDataSource.UpdateUserTitleCallback {
|
|
|
override fun onUpdateUserTitleSuccess(me: User) {
|
|
|
// more logic: tell the adapter to change the title
|
|
|
titleAdapter.currentTitle = titleAdapter.mTitles[titleAdapter.getIndexByTitleName(clickedTitle.name)]
|
|
|
// tell the view that it should show some feedback to the user
|
|
|
view.updateLocalAppUser(me)
|
|
|
view.showUpdateTitleSuccess()
|
|
|
titleAdapter.notifyDataSetChanged()
|
|
|
}
|
|
|
|
|
|
override fun onUpdateUserTitleFailed() {
|
|
|
// tell the view that it should show some feedback to the user
|
|
|
view.showUpdateTitleFailed()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
****Interactor****
|
|
|
|
|
|
The interactor fetches the data from the data source. It's purpose is to forward the data from the source to the presenter e.g. via callbacks. It is used in order to separate logic (which is done in the presenter) and calls to the datasource which in turn accesses the AkamuAPI for network calls.
|
|
|
|
|
|
```kotlin
|
|
|
class TitleInteractor(private val dataSource: TitleDataSourceImpl) : TitleDataSource {
|
|
|
|
|
|
override fun updateUserTitle(title: Title, callback: TitleDataSource.UpdateUserTitleCallback) {
|
|
|
dataSource.updateUserTitle(title, object : TitleDataSource.UpdateUserTitleCallback {
|
|
|
override fun onUpdateUserTitleSuccess(me: User) {
|
|
|
callback.onUpdateUserTitleSuccess(me)
|
|
|
}
|
|
|
|
|
|
override fun onUpdateUserTitleFailed() {
|
|
|
callback.onUpdateUserTitleFailed()
|
|
|
}
|
|
|
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
****Model****
|
|
|
|
|
|
The model stores and delivers the data. We define an interface for our TitleDataSource. It holds another interface for callbacks from the model to the view via the interactor and the presenter.
|
|
|
|
|
|
```kotlin
|
|
|
interface TitleDataSource {
|
|
|
|
|
|
// Interface for callbacks
|
|
|
interface UpdateUserTitleCallback {
|
|
|
fun onUpdateUserTitleSuccess(me: User)
|
|
|
fun onUpdateUserTitleFailed()
|
|
|
}
|
|
|
|
|
|
// Does an API call in an AsyncTask
|
|
|
fun updateUserTitle(title: Title, callback: TitleDataSource.UpdateUserTitleCallback)
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Finally the implementation of this interface does the actual API call and triggers the callbacks. Note, that this is done in an AsyncTask as network calls are not allowed to run on the UI thread.
|
|
|
|
|
|
```kotlin
|
|
|
class TitleDataSourceImpl : TitleDataSource {
|
|
|
|
|
|
override fun updateUserTitle(title: Title, callback: TitleDataSource.UpdateUserTitleCallback) {
|
|
|
// execute a new AsyncTask
|
|
|
val updateUserTitleTask = UpdateUserTitleTask(title, callback)
|
|
|
// Allow parallel execution of AsyncTasks in case other tasks are running
|
|
|
updateUserTitleTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
|
|
|
}
|
|
|
|
|
|
class UpdateUserTitleTask(var title: Title, var callback: TitleDataSource.UpdateUserTitleCallback)
|
|
|
: AsyncTask<Void, Void, Boolean>() {
|
|
|
|
|
|
var me : User? = null
|
|
|
|
|
|
override fun doInBackground(vararg params: Void?): Boolean{
|
|
|
return try {
|
|
|
// prepare the update call
|
|
|
me = V2APIUser.getUser()
|
|
|
me!!.title = title
|
|
|
val patchUser = PatchUser(
|
|
|
me!!.semester,
|
|
|
me!!.avatar.id,
|
|
|
title.id)
|
|
|
// do the update call with the patched user
|
|
|
V2APIUser.updateUser(patchUser)
|
|
|
// for onPostExecute: signalize that call was successful
|
|
|
true
|
|
|
} catch (e: Exception){
|
|
|
e.printStackTrace()
|
|
|
// for onPostExecute: signalize that call was not successful
|
|
|
false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
override fun onPostExecute(success: Boolean?) {
|
|
|
// trigger the callbacks depending on the result of the asynchronous execution
|
|
|
if(success!!){
|
|
|
callback.onUpdateUserTitleSuccess(me)
|
|
|
} else {
|
|
|
callback.onUpdateUserTitleFailed()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
****Conclusion****
|
|
|
So this is the the biggest part of the process for implementing calls to the Akamu game-server using the MVP pattern. It is important to note, that the actual network call actually hides behind the function `V2APIUser.updateUser(patchUser)`. The wiki page [AkamuAPI](Development/AkamuAPI) deals once again more deeply with the network API. |
|
|
\ No newline at end of file |