From 5df4e93ba5cd462a328238739515736178207a64 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Mon, 30 Jan 2017 17:03:54 +0200 Subject: [PATCH 01/28] Modified Profile and User models to be Realm objects for caching them in the future. --- .../koffeemate/data/network/models/Profile.kt | 16 ++++++++------- .../koffeemate/data/network/models/User.kt | 20 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt index 802c471..c2f7898 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt @@ -1,14 +1,16 @@ package com.codemate.koffeemate.data.network.models -class Profile { - lateinit var first_name: String - lateinit var last_name: String - lateinit var real_name: String +import io.realm.RealmObject - var image_72: String? = null - var image_192: String? = null - var image_512: String? = null +open class Profile( + open var first_name: String = "", + open var last_name: String = "", + open var real_name: String = "", + open var image_72: String? = null, + open var image_192: String? = null, + open var image_512: String? = null +) : RealmObject() { val largestAvailableImage: String get() { var imageUrl: String? = image_512 diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt index fe38840..7f73724 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt @@ -1,15 +1,19 @@ package com.codemate.koffeemate.data.network.models +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + class UserListResponse { var members = listOf() } -class User { - lateinit var id: String - lateinit var name: String - lateinit var profile: Profile +open class User( + @PrimaryKey + open var id: String = "", + open var name: String = "", + open var profile: Profile = Profile(), - var real_name: String? = null - var is_bot: Boolean = false - var deleted: Boolean = false -} + open var real_name: String? = null, + open var is_bot: Boolean = false, + open var deleted: Boolean = false +) : RealmObject() \ No newline at end of file From 254555c7d2fdf62085edbb139801b7bc830442a3 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 31 Jan 2017 09:59:46 +0200 Subject: [PATCH 02/28] Moved CoffeeEventRepository interface to the same file RealmCoffeeEventRepository resides in. --- .../data/local/CoffeeEventRepository.kt | 62 ++++++++++++++++++ .../data/local/RealmCoffeeEventRepository.kt | 65 ------------------- .../koffeemate/data/network/models/Profile.kt | 1 - .../koffeemate/data/network/models/User.kt | 1 - 4 files changed, 62 insertions(+), 67 deletions(-) delete mode 100644 app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt index 3ad2100..5df250c 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt @@ -1,6 +1,9 @@ package com.codemate.koffeemate.data.local import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent +import io.realm.Realm +import io.realm.Sort +import java.util.* interface CoffeeEventRepository { fun recordBrewingEvent(userId: String? = ""): CoffeeBrewingEvent @@ -8,4 +11,63 @@ interface CoffeeEventRepository { fun getAccidentCountForUser(userId: String): Long fun getLastBrewingEvent(): CoffeeBrewingEvent? fun getLastBrewingAccident(): CoffeeBrewingEvent? +} + +class RealmCoffeeEventRepository : CoffeeEventRepository { + override fun recordBrewingAccident(userId: String) = with(Realm.getDefaultInstance()) { + var event: CoffeeBrewingEvent? = null + executeTransaction { + event = newEvent(it).apply { + time = System.currentTimeMillis() + isSuccessful = false + this.userId = userId + } + } + + close() + return@with event!! + } + + override fun recordBrewingEvent(userId: String?) = with(Realm.getDefaultInstance()) { + var event: CoffeeBrewingEvent? = null + executeTransaction { + event = newEvent(it).apply { + time = System.currentTimeMillis() + isSuccessful = true + this.userId = userId ?: "" + } + } + + close() + return@with event!! + } + + override fun getAccidentCountForUser(userId: String) = + Realm.getDefaultInstance() + .where(CoffeeBrewingEvent::class.java) + .equalTo("isSuccessful", false) + .equalTo("userId", userId) + .count() + + override fun getLastBrewingEvent(): CoffeeBrewingEvent? { + return Realm.getDefaultInstance() + .where(CoffeeBrewingEvent::class.java) + .equalTo("isSuccessful", true) + .findAllSorted("time", Sort.ASCENDING) + .lastOrNull() + } + + override fun getLastBrewingAccident(): CoffeeBrewingEvent? { + return Realm.getDefaultInstance() + .where(CoffeeBrewingEvent::class.java) + .equalTo("isSuccessful", false) + .findAllSorted("time", Sort.ASCENDING) + .lastOrNull() + } + + private fun newEvent(realm: Realm) = + realm.createObject( + CoffeeBrewingEvent::class.java, + UUID.randomUUID().toString() + ) } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt deleted file mode 100644 index a0848a6..0000000 --- a/app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.codemate.koffeemate.data.local - -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent -import io.realm.Realm -import io.realm.Sort -import java.util.* - -class RealmCoffeeEventRepository : CoffeeEventRepository { - override fun recordBrewingAccident(userId: String) = with(Realm.getDefaultInstance()) { - var event: CoffeeBrewingEvent? = null - executeTransaction { - event = newEvent(it).apply { - time = System.currentTimeMillis() - isSuccessful = false - this.userId = userId - } - } - - close() - return@with event!! - } - - override fun recordBrewingEvent(userId: String?) = with(Realm.getDefaultInstance()) { - var event: CoffeeBrewingEvent? = null - executeTransaction { - event = newEvent(it).apply { - time = System.currentTimeMillis() - isSuccessful = true - this.userId = userId ?: "" - } - } - - close() - return@with event!! - } - - override fun getAccidentCountForUser(userId: String) = - Realm.getDefaultInstance() - .where(CoffeeBrewingEvent::class.java) - .equalTo("isSuccessful", false) - .equalTo("userId", userId) - .count() - - override fun getLastBrewingEvent(): CoffeeBrewingEvent? { - return Realm.getDefaultInstance() - .where(CoffeeBrewingEvent::class.java) - .equalTo("isSuccessful", true) - .findAllSorted("time", Sort.ASCENDING) - .lastOrNull() - } - - override fun getLastBrewingAccident(): CoffeeBrewingEvent? { - return Realm.getDefaultInstance() - .where(CoffeeBrewingEvent::class.java) - .equalTo("isSuccessful", false) - .findAllSorted("time", Sort.ASCENDING) - .lastOrNull() - } - - private fun newEvent(realm: Realm) = - realm.createObject( - CoffeeBrewingEvent::class.java, - UUID.randomUUID().toString() - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt index c2f7898..f21089f 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt @@ -6,7 +6,6 @@ open class Profile( open var first_name: String = "", open var last_name: String = "", open var real_name: String = "", - open var image_72: String? = null, open var image_192: String? = null, open var image_512: String? = null diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt index 7f73724..69918b0 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt @@ -12,7 +12,6 @@ open class User( open var id: String = "", open var name: String = "", open var profile: Profile = Profile(), - open var real_name: String? = null, open var is_bot: Boolean = false, open var deleted: Boolean = false From 80c2ba3607476da5b7b5d52c0dba3ffc86d9912d Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 31 Jan 2017 10:35:53 +0200 Subject: [PATCH 03/28] Created a UserRepository class for adding & getting users. --- .../data/local/RealmUserRepositoryTest.kt | 75 +++++++++++++++++++ .../koffeemate/data/local/UserRepository.kt | 38 ++++++++++ 2 files changed, 113 insertions(+) create mode 100644 app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt create mode 100644 app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt new file mode 100644 index 0000000..003fb7b --- /dev/null +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Codemate Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.codemate.koffeemate.data.local + +import com.codemate.koffeemate.data.network.models.User +import io.realm.Realm +import io.realm.RealmConfiguration +import org.hamcrest.core.IsEqual.equalTo +import org.junit.Assert.assertThat +import org.junit.Before +import org.junit.Test + +class RealmUserRepositoryTest { + val TEST_USERS_UNIQUE = listOf(User(id = "abc123"), User(id = "123abc"), User(id = "a1b2c3")) + val TEST_USERS_DUPLICATE = listOf(User(id = "abc123"), User(id = "abc123"), User(id = "abc123")) + + lateinit var userRepository: UserRepository + + @Before + fun setUp() { + val realmConfig = RealmConfiguration.Builder() + .name("test.realm") + .inMemory() + .build() + + Realm.setDefaultConfiguration(realmConfig) + Realm.getDefaultInstance().executeTransaction(Realm::deleteAll) + + userRepository = RealmUserRepository() + } + + @Test + fun addAll_WhenUsersAreUnique_PersistsAllInDatabase() { + userRepository.addAll(TEST_USERS_UNIQUE) + + val all = userRepository.getAll() + assertThat(all[0].id, equalTo(TEST_USERS_UNIQUE[0].id)) + assertThat(all[1].id, equalTo(TEST_USERS_UNIQUE[1].id)) + assertThat(all[2].id, equalTo(TEST_USERS_UNIQUE[2].id)) + } + + @Test + fun addAll_WhenUsersAreDuplicate_PersistsOnlyOne() { + userRepository.addAll(TEST_USERS_DUPLICATE) + + val all = userRepository.getAll() + assertThat(all.size, equalTo(1)) + } + + @Test + fun addAll_WhenTryingToAddExistingUser_UpdatesIt() { + userRepository.addAll(listOf(User(id = "abc123", name = "John Smith"))) + assertThat(userRepository.getAll().first().name, equalTo("John Smith")) + + userRepository.addAll(listOf(User(id = "abc123", name = "Kevin Doe"))) + + val all = userRepository.getAll() + assertThat(all.size, equalTo(1)) + assertThat(all.first().name, equalTo("Kevin Doe")) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt new file mode 100644 index 0000000..a404a78 --- /dev/null +++ b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Codemate Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.codemate.koffeemate.data.local + +import com.codemate.koffeemate.data.network.models.User +import io.realm.Realm + +interface UserRepository { + fun addAll(users: List) + fun getAll(): List +} + +class RealmUserRepository : UserRepository { + override fun addAll(users: List) { + with(Realm.getDefaultInstance()) { + executeTransaction { copyToRealmOrUpdate(users) } + close() + } + } + + override fun getAll() = Realm.getDefaultInstance() + .where(User::class.java) + .findAll() +} \ No newline at end of file From ae39b0323043abb987ec821eb8c4c5aed4279008 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 31 Jan 2017 12:05:03 +0200 Subject: [PATCH 04/28] Refactored CoffeeEventRepository to have a reference to User table, instead of just a userId field. --- ...ryTest.kt => CoffeeEventRepositoryTest.kt} | 35 ++++++----- ...epositoryTest.kt => UserRepositoryTest.kt} | 2 +- .../data/local/CoffeeEventRepository.kt | 24 +++---- .../data/local/models/CoffeeBrewingEvent.kt | 3 +- .../koffeemate/data/network/models/Profile.kt | 63 +++++++++++++------ .../koffeemate/data/network/models/User.kt | 31 ++++++++- .../koffeemate/ui/main/MainActivity.kt | 18 +++--- .../koffeemate/ui/main/MainPresenter.kt | 14 ++--- .../codemate/koffeemate/ui/main/MainView.kt | 8 +-- .../koffeemate/ui/main/PostAccidentUseCase.kt | 10 +-- .../ui/userselector/UserSelectorActivity.kt | 13 +--- .../koffeemate/ui/main/MainPresenterTest.kt | 15 ++--- .../userselector/PostAccidentUseCaseTest.kt | 28 +++------ 13 files changed, 142 insertions(+), 122 deletions(-) rename app/src/androidTest/java/com/codemate/koffeemate/data/local/{RealmCoffeeEventRepositoryTest.kt => CoffeeEventRepositoryTest.kt} (77%) rename app/src/androidTest/java/com/codemate/koffeemate/data/local/{RealmUserRepositoryTest.kt => UserRepositoryTest.kt} (98%) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt similarity index 77% rename from app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepositoryTest.kt rename to app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt index cd1f1ed..ead89d5 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepositoryTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt @@ -17,6 +17,7 @@ package com.codemate.koffeemate.data.local import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.network.models.User import io.realm.Realm import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo @@ -24,7 +25,7 @@ import org.junit.Assert.assertThat import org.junit.Before import org.junit.Test -class RealmCoffeeEventRepositoryTest { +class CoffeeEventRepositoryTest { lateinit var coffeeEventRepository: RealmCoffeeEventRepository @Before @@ -51,9 +52,9 @@ class RealmCoffeeEventRepositoryTest { @Test fun recordBrewingEvent_WithUserId_SavesUserId() { - coffeeEventRepository.recordBrewingEvent("abc123") + coffeeEventRepository.recordBrewingEvent(User(id = "abc123")) - assertThat(coffeeEventRepository.getLastBrewingEvent()!!.userId, equalTo("abc123")) + assertThat(coffeeEventRepository.getLastBrewingEvent()!!.user!!.id, equalTo("abc123")) } @Test @@ -68,35 +69,35 @@ class RealmCoffeeEventRepositoryTest { @Test fun getLastBrewingEvent_WhenHavingAccidentsAndSuccessfulEvents_ReturnsOnlyLastBrewingEvent() { val lastSuccessfulEvent = coffeeEventRepository.recordBrewingEvent() - coffeeEventRepository.recordBrewingAccident("test") + coffeeEventRepository.recordBrewingAccident(User()) assertThat(coffeeEventRepository.getLastBrewingEvent(), equalTo(lastSuccessfulEvent)) } @Test fun getLastBrewingAccident_ReturnsLastBrewingAccident() { - val userId = "abc123" - coffeeEventRepository.recordBrewingAccident(userId) - coffeeEventRepository.recordBrewingAccident(userId) + val user = User(id = "abc123") + coffeeEventRepository.recordBrewingAccident(user) + coffeeEventRepository.recordBrewingAccident(user) - val lastAccident = coffeeEventRepository.recordBrewingAccident(userId) + val lastAccident = coffeeEventRepository.recordBrewingAccident(user) assertThat(coffeeEventRepository.getLastBrewingAccident(), equalTo(lastAccident)) } @Test fun getAccidentCountForUser_ReturnsAccidentCountForThatSpecificUser() { - val userId = "abc123" - assertThat(coffeeEventRepository.getAccidentCountForUser(userId), equalTo(0L)) + val user = User(id = "abc123") + assertThat(coffeeEventRepository.getAccidentCountForUser(user), equalTo(0L)) - coffeeEventRepository.recordBrewingAccident(userId) - coffeeEventRepository.recordBrewingAccident(userId) - coffeeEventRepository.recordBrewingAccident(userId) + coffeeEventRepository.recordBrewingAccident(user) + coffeeEventRepository.recordBrewingAccident(user) + coffeeEventRepository.recordBrewingAccident(user) - val otherUserId = "someotherid" - coffeeEventRepository.recordBrewingAccident(otherUserId) - coffeeEventRepository.recordBrewingAccident(otherUserId) + val otherUser = User(id = "someotherid") + coffeeEventRepository.recordBrewingAccident(otherUser) + coffeeEventRepository.recordBrewingAccident(otherUser) - assertThat(coffeeEventRepository.getAccidentCountForUser(userId), equalTo(3L)) + assertThat(coffeeEventRepository.getAccidentCountForUser(user), equalTo(3L)) } private fun coffeeEventCount() = diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt similarity index 98% rename from app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt rename to app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt index 003fb7b..ed993f5 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmUserRepositoryTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt @@ -24,7 +24,7 @@ import org.junit.Assert.assertThat import org.junit.Before import org.junit.Test -class RealmUserRepositoryTest { +class UserRepositoryTest { val TEST_USERS_UNIQUE = listOf(User(id = "abc123"), User(id = "123abc"), User(id = "a1b2c3")) val TEST_USERS_DUPLICATE = listOf(User(id = "abc123"), User(id = "abc123"), User(id = "abc123")) diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt index 5df250c..fea2f46 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt @@ -1,26 +1,28 @@ package com.codemate.koffeemate.data.local import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.network.models.User import io.realm.Realm import io.realm.Sort import java.util.* interface CoffeeEventRepository { - fun recordBrewingEvent(userId: String? = ""): CoffeeBrewingEvent - fun recordBrewingAccident(userId: String): CoffeeBrewingEvent - fun getAccidentCountForUser(userId: String): Long + fun recordBrewingEvent(user: User? = null): CoffeeBrewingEvent + fun recordBrewingAccident(user: User): CoffeeBrewingEvent + fun getAccidentCountForUser(user: User): Long + fun getLastBrewingEvent(): CoffeeBrewingEvent? fun getLastBrewingAccident(): CoffeeBrewingEvent? } class RealmCoffeeEventRepository : CoffeeEventRepository { - override fun recordBrewingAccident(userId: String) = with(Realm.getDefaultInstance()) { + override fun recordBrewingEvent(user: User?) = with(Realm.getDefaultInstance()) { var event: CoffeeBrewingEvent? = null executeTransaction { event = newEvent(it).apply { time = System.currentTimeMillis() - isSuccessful = false - this.userId = userId + isSuccessful = true + this.user = if (user != null) copyToRealmOrUpdate(user) else null } } @@ -28,13 +30,13 @@ class RealmCoffeeEventRepository : CoffeeEventRepository { return@with event!! } - override fun recordBrewingEvent(userId: String?) = with(Realm.getDefaultInstance()) { + override fun recordBrewingAccident(user: User) = with(Realm.getDefaultInstance()) { var event: CoffeeBrewingEvent? = null executeTransaction { event = newEvent(it).apply { time = System.currentTimeMillis() - isSuccessful = true - this.userId = userId ?: "" + isSuccessful = false + this.user = copyToRealmOrUpdate(user) } } @@ -42,11 +44,11 @@ class RealmCoffeeEventRepository : CoffeeEventRepository { return@with event!! } - override fun getAccidentCountForUser(userId: String) = + override fun getAccidentCountForUser(user: User) = Realm.getDefaultInstance() .where(CoffeeBrewingEvent::class.java) .equalTo("isSuccessful", false) - .equalTo("userId", userId) + .equalTo("user.id", user.id) .count() override fun getLastBrewingEvent(): CoffeeBrewingEvent? { diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt b/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt index 58a363b..b456c66 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt @@ -1,5 +1,6 @@ package com.codemate.koffeemate.data.local.models +import com.codemate.koffeemate.data.network.models.User import io.realm.RealmObject import io.realm.annotations.PrimaryKey @@ -8,5 +9,5 @@ open class CoffeeBrewingEvent( open var id: String = "", open var time: Long = 0, open var isSuccessful: Boolean = false, - open var userId: String = "" + open var user: User? = null ) : RealmObject() \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt index f21089f..2f62308 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt @@ -1,5 +1,7 @@ package com.codemate.koffeemate.data.network.models +import android.os.Parcel +import android.os.Parcelable import io.realm.RealmObject open class Profile( @@ -9,34 +11,55 @@ open class Profile( open var image_72: String? = null, open var image_192: String? = null, open var image_512: String? = null -) : RealmObject() { +) : RealmObject(), Parcelable { val largestAvailableImage: String - get() { - var imageUrl: String? = image_512 + get() { + var imageUrl: String? = image_512 - if (imageUrl.isNullOrBlank()) { - imageUrl = image_192 - } - - if (imageUrl.isNullOrBlank()) { - imageUrl = image_72 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_192 + } - return imageUrl ?: "" + if (imageUrl.isNullOrBlank()) { + imageUrl = image_72 } + return imageUrl ?: "" + } + val smallestAvailableImage: String - get() { - var imageUrl: String? = image_72 + get() { + var imageUrl: String? = image_72 - if (imageUrl.isNullOrBlank()) { - imageUrl = image_192 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_192 + } - if (imageUrl.isNullOrBlank()) { - imageUrl = image_512 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_512 + } - return imageUrl ?: "" + return imageUrl ?: "" + } + + companion object { + @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): Profile = Profile(source) + override fun newArray(size: Int): Array = arrayOfNulls(size) } + } + + constructor(source: Parcel) : this( + source.readString(), + source.readString(), + source.readString() + ) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel?, flags: Int) { + dest?.writeString(first_name) + dest?.writeString(last_name) + dest?.writeString(real_name) + } } diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt index 69918b0..e4308eb 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt @@ -1,5 +1,7 @@ package com.codemate.koffeemate.data.network.models +import android.os.Parcel +import android.os.Parcelable import io.realm.RealmObject import io.realm.annotations.PrimaryKey @@ -15,4 +17,31 @@ open class User( open var real_name: String? = null, open var is_bot: Boolean = false, open var deleted: Boolean = false -) : RealmObject() \ No newline at end of file +) : RealmObject(), Parcelable { + companion object { + @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): User = User(source) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } + + constructor(source: Parcel) : this( + source.readString(), + source.readString(), + source.readParcelable(ClassLoader.getSystemClassLoader()), + source.readString(), + 1 == source.readInt(), + 1 == source.readInt() + ) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel?, flags: Int) { + dest?.writeString(id) + dest?.writeString(name) + dest?.writeParcelable(profile, 0) + dest?.writeString(real_name) + dest?.writeInt((if (is_bot) 1 else 0)) + dest?.writeInt((if (deleted) 1 else 0)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt index b1ff92d..42cbe86 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt @@ -164,27 +164,23 @@ class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSel override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_CODE_SHAME_USER && resultCode == RESULT_OK && data != null) { - val userId = data.getStringExtra(UserSelectorActivity.RESULT_USER_ID) - val fullName = data.getStringExtra(UserSelectorActivity.RESULT_USER_FULL_NAME) - val firstName = data.getStringExtra(UserSelectorActivity.RESULT_USER_FIRST_NAME) - val largestProfilePicUrl = data.getStringExtra(UserSelectorActivity.RESULT_USER_PROFILE_LARGEST_PIC_URL) - - showPostAccidentAnnouncementPrompt(userId, fullName, firstName, largestProfilePicUrl) + val user = data.getParcelableExtra(UserSelectorActivity.RESULT_USER) + showPostAccidentAnnouncementPrompt(user) } } - override fun showPostAccidentAnnouncementPrompt(userId: String, fullName: String, firstName: String, largestProfilePicUrl: String) { + override fun showPostAccidentAnnouncementPrompt(user: User) { alert { title(R.string.prompt_reset_the_counter) - message(getString(R.string.message_posting_to_slack_fmt, fullName)) + message(getString(R.string.message_posting_to_slack_fmt, user.profile.real_name)) negativeButton(R.string.action_cancel) positiveButton(R.string.action_announce_coffee_accident) { accidentProgress = indeterminateProgressDialog(R.string.progress_message_shaming_person_on_slack) - val comment = getString(R.string.message_congratulations_to_user_fmt, firstName) + val comment = getString(R.string.message_congratulations_to_user_fmt, user.profile.first_name) - Glide.with(this@MainActivity).loadBitmap(largestProfilePicUrl) { profilePic -> - presenter.announceCoffeeBrewingAccident(comment, userId, firstName, profilePic) + Glide.with(this@MainActivity).loadBitmap(user.profile.largestAvailableImage) { profilePic -> + presenter.announceCoffeeBrewingAccident(comment, user, profilePic) } } }.show() diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt index 545723c..242ce69 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt @@ -58,7 +58,7 @@ class MainPresenter @Inject constructor( getView()?.updateCoffeeProgress(0) getView()?.resetCoffeeViewStatus() - coffeeEventRepository.recordBrewingEvent(personBrewingCoffee?.id) + coffeeEventRepository.recordBrewingEvent(personBrewingCoffee) updateLastBrewingEventTime() } @@ -107,13 +107,7 @@ class MainPresenter @Inject constructor( if (coffeePreferences.isAccidentChannelSet()) { personBrewingCoffee?.let { - getView()?.showPostAccidentAnnouncementPrompt( - it.id, - it.profile.real_name, - it.profile.first_name, - it.profile.largestAvailableImage - ) - + getView()?.showPostAccidentAnnouncementPrompt(it) return } @@ -123,10 +117,10 @@ class MainPresenter @Inject constructor( } } - fun announceCoffeeBrewingAccident(comment: String, userId: String, userName: String, profilePic: Bitmap) { + fun announceCoffeeBrewingAccident(comment: String, user: User, profilePic: Bitmap) { ensureViewIsAttached() - postAccidentUseCase.execute(comment, userId, userName, profilePic).subscribe( + postAccidentUseCase.execute(comment, user, profilePic).subscribe( object : Subscriber>() { override fun onNext(response: Response) { getView()?.showAccidentPostedSuccessfullyMessage() diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt index 8ace990..2726134 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt @@ -1,6 +1,7 @@ package com.codemate.koffeemate.ui.main import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.ui.base.MvpView interface MainView : MvpView { @@ -18,12 +19,7 @@ interface MainView : MvpView { fun clearCoffeeBrewingPerson() fun launchUserSelector() - fun showPostAccidentAnnouncementPrompt( - userId: String, - fullName: String, - firstName: String, - largestProfilePicUrl: String - ) + fun showPostAccidentAnnouncementPrompt(user: User) fun showAccidentPostedSuccessfullyMessage() fun showErrorPostingAccidentMessage() } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt index 6bbc060..9f037fc 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt @@ -21,6 +21,7 @@ import com.codemate.koffeemate.common.AwardBadgeCreator import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences import com.codemate.koffeemate.data.network.SlackApi +import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.extensions.toRequestBody import okhttp3.MediaType import okhttp3.MultipartBody @@ -40,17 +41,16 @@ open class PostAccidentUseCase( ) { fun execute( comment: String, - userId: String, - userName: String, + user: User, profilePic: Bitmap ): Observable> { - coffeeEventRepository.recordBrewingAccident(userId) + coffeeEventRepository.recordBrewingAccident(user) - val awardCount = coffeeEventRepository.getAccidentCountForUser(userId) + val awardCount = coffeeEventRepository.getAccidentCountForUser(user) val profilePicWithAward = awardBadgeCreator.createBitmapFileWithAward(profilePic, awardCount) // Evaluates to "johns-certificate.png" etc - val fileName = "${userName.toLowerCase()}s-certificate.png" + val fileName = "${user.profile.first_name.toLowerCase()}s-certificate.png" val channel = coffeePreferences.getAccidentChannel() return slackApi.postImage( diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt index aeafefa..232cee6 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt @@ -9,11 +9,7 @@ import com.codemate.koffeemate.data.network.models.User class UserSelectorActivity : AppCompatActivity(), UserSelectorFragment.UserSelectListener { companion object { - val RESULT_USER_ID = "user_id" - val RESULT_USER_FULL_NAME = "user_full_name" - val RESULT_USER_FIRST_NAME = "user_first_name" - val RESULT_USER_PROFILE_LARGEST_PIC_URL = "user_profile_largest_pic_url" - val RESULT_USER_PROFILE_SMALLEST_PIC_URL = "user_profile_smallest_pic_url" + val RESULT_USER = "user" } override fun onCreate(savedInstanceState: Bundle?) { @@ -29,12 +25,7 @@ class UserSelectorActivity : AppCompatActivity(), UserSelectorFragment.UserSelec override fun onUserSelected(user: User) { val intent = Intent() - intent.putExtra(RESULT_USER_ID, user.id) - intent.putExtra(RESULT_USER_FULL_NAME, user.profile.real_name) - intent.putExtra(RESULT_USER_FIRST_NAME, user.profile.first_name) - intent.putExtra(RESULT_USER_PROFILE_LARGEST_PIC_URL, user.profile.largestAvailableImage) - intent.putExtra(RESULT_USER_PROFILE_SMALLEST_PIC_URL, user.profile.smallestAvailableImage) - + intent.putExtra(RESULT_USER, user) setResult(RESULT_OK, intent) finish() } diff --git a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt index 1abe82f..516fa12 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt @@ -131,7 +131,7 @@ class MainPresenterTest { verify(view).updateCoffeeProgress(0) verify(view).resetCoffeeViewStatus() - verify(mockCoffeeEventRepository).recordBrewingEvent(user.id) + verify(mockCoffeeEventRepository).recordBrewingEvent(user) } @Test @@ -226,12 +226,7 @@ class MainPresenterTest { presenter.personBrewingCoffee = user presenter.launchAccidentReportingScreen() - verify(view).showPostAccidentAnnouncementPrompt( - user.id, - user.profile.real_name, - user.profile.first_name, - user.profile.largestAvailableImage - ) + verify(view).showPostAccidentAnnouncementPrompt(user) verifyNoMoreInteractions(view) } @@ -273,7 +268,7 @@ class MainPresenterTest { .thenReturn(emptySuccessResponse) presenter.personBrewingCoffee = fakeUser() - presenter.announceCoffeeBrewingAccident("", "", "", mock()) + presenter.announceCoffeeBrewingAccident("", fakeUser(), mock()) assertThat(presenter.personBrewingCoffee, nullValue()) } @@ -283,7 +278,7 @@ class MainPresenterTest { whenever(mockSlackApi.postImage(any(), any(), any(), any(), any())) .thenReturn(emptySuccessResponse) - presenter.announceCoffeeBrewingAccident("", "", "", mock()) + presenter.announceCoffeeBrewingAccident("", fakeUser(), mock()) verify(view).showAccidentPostedSuccessfullyMessage() verifyNoMoreInteractions(view) @@ -294,7 +289,7 @@ class MainPresenterTest { whenever(mockSlackApi.postImage(any(), any(), any(), any(), any())) .thenReturn(Observable.error(Throwable())) - presenter.announceCoffeeBrewingAccident("", "", "", mock()) + presenter.announceCoffeeBrewingAccident("", fakeUser(), mock()) verify(view).showErrorPostingAccidentMessage() } diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt index e6f883a..20d3a60 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt @@ -24,17 +24,11 @@ import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.SlackService -import com.codemate.koffeemate.data.network.models.Profile -import com.codemate.koffeemate.data.network.models.User -import com.codemate.koffeemate.testutils.RegexMatcher +import com.codemate.koffeemate.testutils.RegexMatcher.Companion.matchesPattern import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile import com.codemate.koffeemate.ui.main.PostAccidentUseCase -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.verify -import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions -import com.nhaarman.mockito_kotlin.whenever -import okhttp3.Dispatcher +import com.nhaarman.mockito_kotlin.* import okhttp3.ResponseBody import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -50,8 +44,6 @@ import rx.observers.TestSubscriber import rx.schedulers.Schedulers class PostAccidentUseCaseTest { - val TEST_USER_ID = "abc123" - @Mock lateinit var mockCoffeePreferences: CoffeePreferences @@ -79,7 +71,7 @@ class PostAccidentUseCaseTest { mockCoffeePreferences.preferences = mock() whenever(mockCoffeePreferences.getAccidentChannel()).thenReturn("test-channel") - whenever(mockCoffeeEventRepository.getAccidentCountForUser(TEST_USER_ID)).thenReturn(1) + whenever(mockCoffeeEventRepository.getAccidentCountForUser(any())).thenReturn(1) whenever(mockAwardBadgeCreator.createBitmapFileWithAward(mockBitmap, 1)) .thenReturn(getResourceFile("images/empty.png")) @@ -105,26 +97,26 @@ class PostAccidentUseCaseTest { @Test fun announceCoffeeBrewingAccident_ShouldMakeCorrectRequest() { val user = fakeUser() - useCase.execute("Test comment", user.id, user.profile.first_name, mockBitmap).subscribe(testSubscriber) + useCase.execute("Test comment", user, mockBitmap).subscribe(testSubscriber) // TODO: There has to be a better way to verify these multipart post params, right? :S val requestBody = mockServer.takeRequest().body.readUtf8() assertThat(requestBody, StringContains.containsString("filename=\"jormas-certificate.png\"")) - assertThat(requestBody, RegexMatcher.matchesPattern(".*channels.*test-channel.*")) - assertThat(requestBody, RegexMatcher.matchesPattern(".*initial_comment.*Test comment.*")) - assertThat(requestBody, RegexMatcher.matchesPattern(".*token.*${BuildConfig.SLACK_AUTH_TOKEN}.*")) + assertThat(requestBody, matchesPattern(".*channels.*test-channel.*")) + assertThat(requestBody, matchesPattern(".*initial_comment.*Test comment.*")) + assertThat(requestBody, matchesPattern(".*token.*${BuildConfig.SLACK_AUTH_TOKEN}.*")) } @Test fun announceCoffeeBrewingAccident_WhenSuccessful_NotifiesUIAndStoresEvent() { val user = fakeUser() - useCase.execute("", user.id, user.profile.first_name, mockBitmap).subscribe(testSubscriber) + useCase.execute("", user, mockBitmap).subscribe(testSubscriber) testSubscriber.assertValueCount(1) testSubscriber.assertCompleted() - verify(mockCoffeeEventRepository).recordBrewingAccident(user.id) - verify(mockCoffeeEventRepository).getAccidentCountForUser(TEST_USER_ID) + verify(mockCoffeeEventRepository).recordBrewingAccident(user) + verify(mockCoffeeEventRepository).getAccidentCountForUser(user) verifyNoMoreInteractions(mockCoffeeEventRepository) } } \ No newline at end of file From 4e9fa70028a5ae72d3a71b5b5488fe3427040f1c Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 31 Jan 2017 12:23:20 +0200 Subject: [PATCH 05/28] Started writing the most basic Realm migration to the CoffeeBrewingEvent table. --- app/src/androidTest/assets/schema-v0.realm | Bin 0 -> 4096 bytes .../data/local/RealmMigrationTest.kt | 57 +++++++++++ .../com/codemate/koffeemate/KoffeemateApp.kt | 15 ++- .../koffeemate/data/local/Migration.kt | 92 ++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 app/src/androidTest/assets/schema-v0.realm create mode 100644 app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt create mode 100644 app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt diff --git a/app/src/androidTest/assets/schema-v0.realm b/app/src/androidTest/assets/schema-v0.realm new file mode 100644 index 0000000000000000000000000000000000000000..b65f0f2c382cf1853f886b7aa5f992cacc01c603 GIT binary patch literal 4096 zcmeHHJ!o536h8Of_aoPJQSnei38;w;F5$s@vMfDqPFzFk$9}dBjQ*>k`uCN zEOZf>yv0MI9y1C;LPvGWs4=5E6f$(^Qs{S{?vr0y3?4G-f+e4yd(L;h?_7;6%FY{m z_I=m+>b7N(xP|ixk?KC2`7imr_9z(yaWDujiGLOy2L1m1-ASajueL1D`c|A}g(zLV74vqtiTobd7Y%e3;jwy4n&HYPaQ~2;`TTbZCJdQGg z{U77|ankFzJ4abw?&tdtL{|*;ea6qd`RID$q!35G{7Ib6>w`z**(^QF2ag8u70=3` z{Rm^s&vLu}&2bbZ{eF6U2(LQsC%vyP?ksU%;4r{lnQv(vY9gkhf`&RSH}=anMsoFM zwvoFZ#A!2XhD|5gu6j=+&88Q}bvpmTzdpRqbup+^8ams|*N!}pXBr3elC?(SHq$uZI0jV{++c#(#)`|A z>wo<9Jshv}H~YEnO^CvWZTyRh*|CE#a=hwRKDRlRQ+X~)l{FB~XB}L*nR&FW?uoJ3 zfroAI@q z@%IHT!FUEwKhN>VHB(R?$4nyMCyh^hS=gf`#C7@XKI)q#bJO(AJLWy}y}sC=2bm|w z!TU2bYjw7&4Y??oj|Gwj{aRNMN#v6^3dB7ad z;a%p@cJ_`-XvlW{jwMKi4pOaO + if (previousUserId.isNotBlank()) { + realm.createObject("User", previousUserId) + } + } + } + .removeField("userId") + } + } +} \ No newline at end of file From 916309ecedbf1971dbb54999209994b290484dd4 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 31 Jan 2017 19:34:11 +0200 Subject: [PATCH 06/28] Finally wrote passing tests for the migrations. --- .../data/local/RealmMigrationTest.kt | 68 ++++--- .../androidTest/java/io/realm/TestHelper.java | 40 ++++ .../rule/TestRealmConfigurationFactory.java | 179 ++++++++++++++++++ .../koffeemate/data/local/Migration.kt | 26 ++- 4 files changed, 286 insertions(+), 27 deletions(-) create mode 100644 app/src/androidTest/java/io/realm/TestHelper.java create mode 100644 app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 50541cd..1c72a05 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -16,42 +16,64 @@ package com.codemate.koffeemate.data.local +import android.content.Context +import android.support.test.InstrumentationRegistry import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent -import io.realm.DynamicRealm -import io.realm.FieldAttribute import io.realm.Realm -import io.realm.RealmConfiguration +import io.realm.rule.TestRealmConfigurationFactory import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsNull.nullValue import org.junit.Assert.assertThat import org.junit.Before +import org.junit.Rule import org.junit.Test class RealmMigrationTest { + lateinit var context: Context + + @Rule @JvmField + val configFactory = TestRealmConfigurationFactory() + + @Before + fun setUp() { + context = InstrumentationRegistry.getContext() + } + /************************************************************* * Migration tests from schema version 0 to 1 *************************************************************/ @Test - fun recordedBrewingAccidentsPersist() { - // TODO: These don't work yet - // val realm = getRealmForSchemaVersion(1) - // val all = realm.where(CoffeeBrewingEvent::class.java).findAll() - // assertThat(all.size, equalTo(3)) - } + fun testMigrationFromVersionZeroToOne() { + val schemaName = "schema-v0.realm" + val config = configFactory.createConfigurationBuilder() + .name(schemaName) + .schemaVersion(1) + .migration(Migration()) + .build() - /************************************************************* - * Helper functions - *************************************************************/ - private fun getRealmForSchemaVersion(schemaVersion: Long): Realm { - val config = buildRealmConfig(schemaVersion) - return Realm.getInstance(config) - } + configFactory.copyRealmFromAssets(context, schemaName, schemaName) + + val realm = Realm.getInstance(config) + + val all = realm.where(CoffeeBrewingEvent::class.java).findAll() + assertThat(all.size, equalTo(3)) - private fun buildRealmConfig(schemaVersion: Long) = - RealmConfiguration.Builder() - .assetFile("schema-v0.realm") - .name("test.realm") - .schemaVersion(schemaVersion) - .migration(Migration()) - .build() + val first = all[0] + assertThat(first.id, equalTo("adf9c9b9-e521-462f-9d67-ff2a11d7b62c")) + assertThat(first.time, equalTo(1485872637115L)) + assertThat(first.isSuccessful, equalTo(true)) + assertThat(first.user, nullValue()) + + val second = all[1] + assertThat(second.id, equalTo("0e742762-7181-4bc0-b7b5-d1ff68991dd6")) + assertThat(second.time, equalTo(1485872637117L)) + assertThat(second.isSuccessful, equalTo(true)) + assertThat(second.user!!.id, equalTo("abc-123")) + + val third = all[2] + assertThat(third.id, equalTo("480bb3b9-a01f-45cb-87cd-113465d4038a")) + assertThat(third.time, equalTo(1485872637118L)) + assertThat(third.isSuccessful, equalTo(false)) + assertThat(third.user!!.id, equalTo("abc-123")) + } } diff --git a/app/src/androidTest/java/io/realm/TestHelper.java b/app/src/androidTest/java/io/realm/TestHelper.java new file mode 100644 index 0000000..f66ce05 --- /dev/null +++ b/app/src/androidTest/java/io/realm/TestHelper.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm; + +import static junit.framework.Assert.fail; + +public class TestHelper { + /** + * Wait and check if all tasks in BaseRealm.asyncTaskExecutor can be finished in 5 seconds, otherwise fail the test. + */ + public static void waitRealmThreadExecutorFinish() { + int counter = 50; + while (counter > 0) { + if (BaseRealm.asyncTaskExecutor.getActiveCount() == 0) { + return; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + counter--; + } + fail("'BaseRealm.asyncTaskExecutor' is not finished in " + counter/10 + " seconds"); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java new file mode 100644 index 0000000..f5d9907 --- /dev/null +++ b/app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -0,0 +1,179 @@ +/* + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.rule; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import org.junit.rules.TemporaryFolder; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.TestHelper; + +import static org.junit.Assert.assertTrue; + +/** + * Rule that creates the {@link RealmConfiguration } in a temporary directory and deletes the Realm created with that + * configuration once the test finishes. Be sure to close all Realm instances before finishing the test. Otherwise + * {@link Realm#deleteRealm(RealmConfiguration)} will throw an exception in the {@link #after()} method. + * The temp directory will be deleted regardless if the {@link Realm#deleteRealm(RealmConfiguration)} fails or not. + */ +public class TestRealmConfigurationFactory extends TemporaryFolder { + private Map map = new ConcurrentHashMap(); + private Set configurations = Collections.newSetFromMap(map); + protected boolean unitTestFailed = false; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + before(); + try { + base.evaluate(); + } catch (Throwable throwable) { + unitTestFailed = true; + throw throwable; + } finally { + after(); + } + } + }; + } + + @Override + protected void before() throws Throwable { + super.before(); + Realm.init(InstrumentationRegistry.getTargetContext()); + } + + @Override + protected void after() { + // Wait all async tasks done to ensure successful deleteRealm call. + // This will throw when timeout. And the reason of timeout needs to be solved properly. + TestHelper.waitRealmThreadExecutorFinish(); + + try { + for (RealmConfiguration configuration : configurations) { + Realm.deleteRealm(configuration); + } + } catch (IllegalStateException e) { + // Only throw the exception caused by deleting the opened Realm if the test case itself doesn't throw. + if (!unitTestFailed) { + throw e; + } + } finally { + // This will delete the temp directory. + super.after(); + } + } + + public RealmConfiguration createConfiguration() { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .directory(getRoot()) + .build(); + + configurations.add(configuration); + return configuration; + } + + public RealmConfiguration createConfiguration(String subDir, String name) { + final File folder = new File(getRoot(), subDir); + assertTrue(folder.mkdirs()); + RealmConfiguration configuration = new RealmConfiguration.Builder() + .directory(folder) + .name(name) + .build(); + + configurations.add(configuration); + return configuration; + } + + public RealmConfiguration createConfiguration(String name) { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .directory(getRoot()) + .name(name) + .build(); + + configurations.add(configuration); + return configuration; + } + + public RealmConfiguration createConfiguration(String name, byte[] key) { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .directory(getRoot()) + .name(name) + .encryptionKey(key) + .build(); + + configurations.add(configuration); + return configuration; + } + + public RealmConfiguration.Builder createConfigurationBuilder() { + return new RealmConfiguration.Builder().directory(getRoot()); + } + + // Copies a Realm file from assets to temp dir + public void copyRealmFromAssets(Context context, String realmPath, String newName) throws IOException { + RealmConfiguration config = new RealmConfiguration.Builder() + .directory(getRoot()) + .name(newName) + .build(); + + copyRealmFromAssets(context, realmPath, config); + } + + public void copyRealmFromAssets(Context context, String realmPath, RealmConfiguration config) throws IOException { + // Delete the existing file before copy + Realm.deleteRealm(config); + + File outFile = new File(config.getRealmDirectory(), config.getRealmFileName()); + + InputStream is = null; + FileOutputStream os = null; + try { + is = context.getAssets().open(realmPath); + os = new FileOutputStream(outFile); + + byte[] buf = new byte[1024]; + int bytesRead; + while ((bytesRead = is.read(buf)) > -1) { + os.write(buf, 0, bytesRead); + } + } finally { + if (is != null) { + try { is.close(); } catch (IOException ignore) {} + } + if (os != null) { + try { os.close(); } catch (IOException ignore) {} + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt index 49fa1d4..98ad90f 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt @@ -17,8 +17,10 @@ package com.codemate.koffeemate.data.local import io.realm.DynamicRealm +import io.realm.DynamicRealmObject import io.realm.FieldAttribute import io.realm.RealmMigration +import io.realm.exceptions.RealmPrimaryKeyConstraintException class Migration : RealmMigration { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -60,7 +62,7 @@ class Migration : RealmMigration { image_192: String image_512: String *************************************************************/ - if (oldVersion < 0L) { + if (oldVersion == 0L) { val profileSchema = schema.create("Profile") .addField("first_name", String::class.java) .addField("last_name", String::class.java) @@ -79,10 +81,26 @@ class Migration : RealmMigration { schema.get("CoffeeBrewingEvent") .addRealmObjectField("user", userSchema) - .transform { - it.getString("userId")?.let { previousUserId -> + .transform { brewingEvent -> + brewingEvent.getString("userId")?.let { previousUserId -> if (previousUserId.isNotBlank()) { - realm.createObject("User", previousUserId) + var user: DynamicRealmObject? + + // DynamicRealm doesn't allow us create duplicate User objects, + // since the id field is a primary key. insertOrUpdate() and similar + // methods are unavailable when using DynamicRealms. + // + // Try-catch is the only way to handle the migration of userIds to + // users in this case. + try { + user = realm.createObject("User", previousUserId) + } catch (e: RealmPrimaryKeyConstraintException) { + user = realm.where("User") + .equalTo("id", previousUserId) + .findFirst() + } + + brewingEvent.setObject("user", user!!) } } } From 45fde0e49bfcf32f79191638cdbd5502a4d48aa0 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 10:07:10 +0200 Subject: [PATCH 07/28] Copied the only Java function we needed, and deleted the unused stuff. --- ...ema-v0.realm => sample-db-schema-v0.realm} | Bin .../data/local/RealmMigrationTest.kt | 55 +++++- .../androidTest/java/io/realm/TestHelper.java | 40 ---- .../rule/TestRealmConfigurationFactory.java | 179 ------------------ 4 files changed, 45 insertions(+), 229 deletions(-) rename app/src/androidTest/assets/{schema-v0.realm => sample-db-schema-v0.realm} (100%) delete mode 100644 app/src/androidTest/java/io/realm/TestHelper.java delete mode 100644 app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java diff --git a/app/src/androidTest/assets/schema-v0.realm b/app/src/androidTest/assets/sample-db-schema-v0.realm similarity index 100% rename from app/src/androidTest/assets/schema-v0.realm rename to app/src/androidTest/assets/sample-db-schema-v0.realm diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 1c72a05..0cae295 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -20,20 +20,20 @@ import android.content.Context import android.support.test.InstrumentationRegistry import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent import io.realm.Realm -import io.realm.rule.TestRealmConfigurationFactory +import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsNull.nullValue import org.junit.Assert.assertThat import org.junit.Before -import org.junit.Rule import org.junit.Test +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream class RealmMigrationTest { lateinit var context: Context - @Rule @JvmField - val configFactory = TestRealmConfigurationFactory() - @Before fun setUp() { context = InstrumentationRegistry.getContext() @@ -44,15 +44,16 @@ class RealmMigrationTest { *************************************************************/ @Test fun testMigrationFromVersionZeroToOne() { - val schemaName = "schema-v0.realm" - val config = configFactory.createConfigurationBuilder() - .name(schemaName) + val config = RealmConfiguration.Builder() + .name("test.realm") .schemaVersion(1) .migration(Migration()) .build() - configFactory.copyRealmFromAssets(context, schemaName, schemaName) - + // The "sample-db-schema-v0.realm" contains three sample records, + // in the old database schema, which used userIds instead of User + // objects. + copyRealmFromAssets(context, "sample-db-schema-v0.realm", config) val realm = Realm.getInstance(config) val all = realm.where(CoffeeBrewingEvent::class.java).findAll() @@ -76,4 +77,38 @@ class RealmMigrationTest { assertThat(third.isSuccessful, equalTo(false)) assertThat(third.user!!.id, equalTo("abc-123")) } + + @Throws(IOException::class) + fun copyRealmFromAssets(context: Context, realmPath: String, config: RealmConfiguration) { + Realm.deleteRealm(config) + + val outFile = File(config.realmDirectory, config.realmFileName) + var inputStream: InputStream? = null + var os: FileOutputStream? = null + + try { + inputStream = context.assets.open(realmPath) + os = FileOutputStream(outFile) + + val buf = ByteArray(1024) + var bytesRead: Int = 0 + while (bytesRead > -1) { + os.write(buf, 0, bytesRead) + bytesRead = inputStream!!.read(buf) + } + } finally { + if (inputStream != null) { + try { + inputStream.close() + } catch (ignore: IOException) { + } + } + if (os != null) { + try { + os.close() + } catch (ignore: IOException) { + } + } + } + } } diff --git a/app/src/androidTest/java/io/realm/TestHelper.java b/app/src/androidTest/java/io/realm/TestHelper.java deleted file mode 100644 index f66ce05..0000000 --- a/app/src/androidTest/java/io/realm/TestHelper.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm; - -import static junit.framework.Assert.fail; - -public class TestHelper { - /** - * Wait and check if all tasks in BaseRealm.asyncTaskExecutor can be finished in 5 seconds, otherwise fail the test. - */ - public static void waitRealmThreadExecutorFinish() { - int counter = 50; - while (counter > 0) { - if (BaseRealm.asyncTaskExecutor.getActiveCount() == 0) { - return; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - counter--; - } - fail("'BaseRealm.asyncTaskExecutor' is not finished in " + counter/10 + " seconds"); - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java deleted file mode 100644 index f5d9907..0000000 --- a/app/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.rule; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; - -import org.junit.rules.TemporaryFolder; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.TestHelper; - -import static org.junit.Assert.assertTrue; - -/** - * Rule that creates the {@link RealmConfiguration } in a temporary directory and deletes the Realm created with that - * configuration once the test finishes. Be sure to close all Realm instances before finishing the test. Otherwise - * {@link Realm#deleteRealm(RealmConfiguration)} will throw an exception in the {@link #after()} method. - * The temp directory will be deleted regardless if the {@link Realm#deleteRealm(RealmConfiguration)} fails or not. - */ -public class TestRealmConfigurationFactory extends TemporaryFolder { - private Map map = new ConcurrentHashMap(); - private Set configurations = Collections.newSetFromMap(map); - protected boolean unitTestFailed = false; - - @Override - public Statement apply(final Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - before(); - try { - base.evaluate(); - } catch (Throwable throwable) { - unitTestFailed = true; - throw throwable; - } finally { - after(); - } - } - }; - } - - @Override - protected void before() throws Throwable { - super.before(); - Realm.init(InstrumentationRegistry.getTargetContext()); - } - - @Override - protected void after() { - // Wait all async tasks done to ensure successful deleteRealm call. - // This will throw when timeout. And the reason of timeout needs to be solved properly. - TestHelper.waitRealmThreadExecutorFinish(); - - try { - for (RealmConfiguration configuration : configurations) { - Realm.deleteRealm(configuration); - } - } catch (IllegalStateException e) { - // Only throw the exception caused by deleting the opened Realm if the test case itself doesn't throw. - if (!unitTestFailed) { - throw e; - } - } finally { - // This will delete the temp directory. - super.after(); - } - } - - public RealmConfiguration createConfiguration() { - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(getRoot()) - .build(); - - configurations.add(configuration); - return configuration; - } - - public RealmConfiguration createConfiguration(String subDir, String name) { - final File folder = new File(getRoot(), subDir); - assertTrue(folder.mkdirs()); - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(folder) - .name(name) - .build(); - - configurations.add(configuration); - return configuration; - } - - public RealmConfiguration createConfiguration(String name) { - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(getRoot()) - .name(name) - .build(); - - configurations.add(configuration); - return configuration; - } - - public RealmConfiguration createConfiguration(String name, byte[] key) { - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(getRoot()) - .name(name) - .encryptionKey(key) - .build(); - - configurations.add(configuration); - return configuration; - } - - public RealmConfiguration.Builder createConfigurationBuilder() { - return new RealmConfiguration.Builder().directory(getRoot()); - } - - // Copies a Realm file from assets to temp dir - public void copyRealmFromAssets(Context context, String realmPath, String newName) throws IOException { - RealmConfiguration config = new RealmConfiguration.Builder() - .directory(getRoot()) - .name(newName) - .build(); - - copyRealmFromAssets(context, realmPath, config); - } - - public void copyRealmFromAssets(Context context, String realmPath, RealmConfiguration config) throws IOException { - // Delete the existing file before copy - Realm.deleteRealm(config); - - File outFile = new File(config.getRealmDirectory(), config.getRealmFileName()); - - InputStream is = null; - FileOutputStream os = null; - try { - is = context.getAssets().open(realmPath); - os = new FileOutputStream(outFile); - - byte[] buf = new byte[1024]; - int bytesRead; - while ((bytesRead = is.read(buf)) > -1) { - os.write(buf, 0, bytesRead); - } - } finally { - if (is != null) { - try { is.close(); } catch (IOException ignore) {} - } - if (os != null) { - try { os.close(); } catch (IOException ignore) {} - } - } - } -} \ No newline at end of file From ad16239801c90ca3d14df2ccd1b5e16d4328fb75 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 10:16:35 +0200 Subject: [PATCH 08/28] Refactored the copy-pasted Java blurp to more idiomatic Kotlin. --- .../data/local/RealmMigrationTest.kt | 31 +++---------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 0cae295..50f480e 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -27,9 +27,7 @@ import org.junit.Assert.assertThat import org.junit.Before import org.junit.Test import java.io.File -import java.io.FileOutputStream import java.io.IOException -import java.io.InputStream class RealmMigrationTest { lateinit var context: Context @@ -82,32 +80,11 @@ class RealmMigrationTest { fun copyRealmFromAssets(context: Context, realmPath: String, config: RealmConfiguration) { Realm.deleteRealm(config) - val outFile = File(config.realmDirectory, config.realmFileName) - var inputStream: InputStream? = null - var os: FileOutputStream? = null + context.assets.open(realmPath).use { inputStream -> + val outFile = File(config.realmDirectory, config.realmFileName) - try { - inputStream = context.assets.open(realmPath) - os = FileOutputStream(outFile) - - val buf = ByteArray(1024) - var bytesRead: Int = 0 - while (bytesRead > -1) { - os.write(buf, 0, bytesRead) - bytesRead = inputStream!!.read(buf) - } - } finally { - if (inputStream != null) { - try { - inputStream.close() - } catch (ignore: IOException) { - } - } - if (os != null) { - try { - os.close() - } catch (ignore: IOException) { - } + outFile.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) } } } From a74e7e87e4e2c578905037aeda6d8c25ad9879d8 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 10:16:35 +0200 Subject: [PATCH 09/28] Refactored the copy-pasted Java blurp to more idiomatic Kotlin. --- .../data/local/RealmMigrationTest.kt | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 0cae295..10f3c58 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -27,9 +27,7 @@ import org.junit.Assert.assertThat import org.junit.Before import org.junit.Test import java.io.File -import java.io.FileOutputStream import java.io.IOException -import java.io.InputStream class RealmMigrationTest { lateinit var context: Context @@ -45,7 +43,7 @@ class RealmMigrationTest { @Test fun testMigrationFromVersionZeroToOne() { val config = RealmConfiguration.Builder() - .name("test.realm") + .name("migration-test.realm") .schemaVersion(1) .migration(Migration()) .build() @@ -82,32 +80,11 @@ class RealmMigrationTest { fun copyRealmFromAssets(context: Context, realmPath: String, config: RealmConfiguration) { Realm.deleteRealm(config) - val outFile = File(config.realmDirectory, config.realmFileName) - var inputStream: InputStream? = null - var os: FileOutputStream? = null + context.assets.open(realmPath).use { inputStream -> + val outFile = File(config.realmDirectory, config.realmFileName) - try { - inputStream = context.assets.open(realmPath) - os = FileOutputStream(outFile) - - val buf = ByteArray(1024) - var bytesRead: Int = 0 - while (bytesRead > -1) { - os.write(buf, 0, bytesRead) - bytesRead = inputStream!!.read(buf) - } - } finally { - if (inputStream != null) { - try { - inputStream.close() - } catch (ignore: IOException) { - } - } - if (os != null) { - try { - os.close() - } catch (ignore: IOException) { - } + outFile.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) } } } From a4e299c61076ca827f692494051a8cd761f7d34f Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 10:46:06 +0200 Subject: [PATCH 10/28] Closed the realm after migration tests, just in case. --- .../com/codemate/koffeemate/data/local/RealmMigrationTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 10f3c58..0507f75 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -74,6 +74,8 @@ class RealmMigrationTest { assertThat(third.time, equalTo(1485872637118L)) assertThat(third.isSuccessful, equalTo(false)) assertThat(third.user!!.id, equalTo("abc-123")) + + realm.close() } @Throws(IOException::class) From 50228078e40a3e9ac00b9c72cbae86865052995c Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 15:05:57 +0200 Subject: [PATCH 11/28] Started doing some simple caching logic for the users. --- .../koffeemate/data/local/UserRepository.kt | 2 +- .../koffeemate/di/modules/AppModule.kt | 4 +++- .../koffeemate/di/modules/PersistenceModule.kt | 8 +++++--- .../ui/userselector/LoadUsersUseCase.kt | 18 ++++++++++++++---- .../ui/userselector/LoadUsersUseCaseTest.kt | 3 +++ .../userselector/UserSelectorPresenterTest.kt | 2 ++ 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt index a404a78..61b3d52 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt @@ -32,7 +32,7 @@ class RealmUserRepository : UserRepository { } } - override fun getAll() = Realm.getDefaultInstance() + override fun getAll(): List = Realm.getDefaultInstance() .where(User::class.java) .findAll() } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt index c4d66fa..269756a 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt @@ -23,6 +23,7 @@ import com.codemate.koffeemate.common.AwardBadgeCreator import com.codemate.koffeemate.common.BrewingProgressUpdater import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences +import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.ui.userselector.LoadUsersUseCase import com.codemate.koffeemate.ui.main.PostAccidentUseCase @@ -66,8 +67,9 @@ class AppModule(val app: KoffeemateApp) { @Provides @Singleton - fun provideLoadUsersUseCase(slackApi: SlackApi) = + fun provideLoadUsersUseCase(userRepository: UserRepository, slackApi: SlackApi) = LoadUsersUseCase( + userRepository, slackApi, Schedulers.newThread(), AndroidSchedulers.mainThread() diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt index b0ea703..dd4431d 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt @@ -17,9 +17,7 @@ package com.codemate.koffeemate.di.modules import android.content.Context -import com.codemate.koffeemate.data.local.CoffeeEventRepository -import com.codemate.koffeemate.data.local.CoffeePreferences -import com.codemate.koffeemate.data.local.RealmCoffeeEventRepository +import com.codemate.koffeemate.data.local.* import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -33,4 +31,8 @@ class PersistenceModule { @Provides @Singleton fun provideCoffeeEventRepository(): CoffeeEventRepository = RealmCoffeeEventRepository() + + @Provides + @Singleton + fun provideUserRepository(): UserRepository = RealmUserRepository() } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt index 2c62d74..b364121 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt @@ -17,19 +17,23 @@ package com.codemate.koffeemate.ui.userselector import com.codemate.koffeemate.BuildConfig +import com.codemate.koffeemate.data.local.RealmUserRepository +import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.models.User -import com.codemate.koffeemate.data.network.models.UserListResponse import rx.Observable import rx.Scheduler open class LoadUsersUseCase( + var userRepository: UserRepository, var slackApi: SlackApi, var subscriber: Scheduler, var observer: Scheduler ) { fun execute(): Observable> { - return slackApi.getUsers(BuildConfig.SLACK_AUTH_TOKEN) + val cachedUsers = Observable.just(userRepository.getAll()) + val networkUsers = slackApi.getUsers(BuildConfig.SLACK_AUTH_TOKEN) + .flatMap { Observable.just(it.members) } .subscribeOn(subscriber) .observeOn(observer) .map { @@ -37,10 +41,16 @@ open class LoadUsersUseCase( it.profile.real_name } } + .doOnNext { userRepository.addAll(it) } + + return Observable + .concat(cachedUsers, networkUsers) + // TODO: only take the first (which is cached users) if it's fresh enough. + .first() } - private fun filterNonCompanyUsers(response: UserListResponse): List { - return response.members.filter { + private fun filterNonCompanyUsers(response: List): List { + return response.filter { !it.is_bot // At Codemate, profiles starting with "Ext-" aren't employees, // but customers instead: they don't hang out in the office. diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt index db7af43..ce0f872 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt @@ -17,10 +17,12 @@ package com.codemate.koffeemate.ui.userselector import com.codemate.koffeemate.BuildConfig +import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.SlackService import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.testutils.getResourceFile +import com.nhaarman.mockito_kotlin.mock import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.hamcrest.core.IsEqual.equalTo @@ -44,6 +46,7 @@ class LoadUsersUseCaseTest { slackApi = SlackService.getApi(mockServer.url("/")) useCase = LoadUsersUseCase( + mock(), slackApi, Schedulers.immediate(), Schedulers.immediate() diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt index 59f1bb0..658d96a 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import com.codemate.koffeemate.common.AwardBadgeCreator import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences +import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.models.Profile import com.codemate.koffeemate.data.network.models.User @@ -51,6 +52,7 @@ class UserSelectorPresenterTest { MockitoAnnotations.initMocks(this) val loadUsersUseCase = LoadUsersUseCase( + mock(), mockSlackApi, Schedulers.immediate(), Schedulers.immediate() From 64695131201e5157b2f281314ffa4f2681890b16 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 20:00:05 +0200 Subject: [PATCH 12/28] Bugfix: The "first()" function in LoadUsersUseCase didn't have any condition, so it just returned empty user list from the database. --- .../codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt index b364121..633e53d 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt @@ -17,7 +17,6 @@ package com.codemate.koffeemate.ui.userselector import com.codemate.koffeemate.BuildConfig -import com.codemate.koffeemate.data.local.RealmUserRepository import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.models.User @@ -46,7 +45,7 @@ open class LoadUsersUseCase( return Observable .concat(cachedUsers, networkUsers) // TODO: only take the first (which is cached users) if it's fresh enough. - .first() + .first { it.isNotEmpty() } } private fun filterNonCompanyUsers(response: List): List { From a96b49be7b34946354aa4f8fb22c2efa287e64a3 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 20:32:30 +0200 Subject: [PATCH 13/28] Finished the user caching: now cached users are always returned if they're not over half a day old. --- .../data/local/RealmMigrationTest.kt | 7 +++++++ .../koffeemate/data/local/Migration.kt | 6 +++++- .../koffeemate/data/network/models/User.kt | 15 +++++++++++++-- .../ui/userselector/LoadUsersUseCase.kt | 19 ++++++++++++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 0507f75..0c97a3a 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -22,6 +22,7 @@ import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent import io.realm.Realm import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo +import org.hamcrest.core.IsNot.not import org.hamcrest.core.IsNull.nullValue import org.junit.Assert.assertThat import org.junit.Before @@ -68,12 +69,18 @@ class RealmMigrationTest { assertThat(second.time, equalTo(1485872637117L)) assertThat(second.isSuccessful, equalTo(true)) assertThat(second.user!!.id, equalTo("abc-123")) + assertThat(second.user!!.last_updated, not(equalTo(0L))) val third = all[2] assertThat(third.id, equalTo("480bb3b9-a01f-45cb-87cd-113465d4038a")) assertThat(third.time, equalTo(1485872637118L)) assertThat(third.isSuccessful, equalTo(false)) assertThat(third.user!!.id, equalTo("abc-123")) + assertThat(second.user!!.last_updated, not(equalTo(0L))) + + // Make sure we don't generate different timestamps for the users in + // this new schema. + assertThat(second.user!!.last_updated, equalTo(third.user!!.last_updated)) realm.close() } diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt index 98ad90f..f9ef371 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt @@ -53,6 +53,7 @@ class Migration : RealmMigration { real_name: String is_bot: Boolean deleted: Boolean + last_updated: Long class Profile first_name: String @@ -78,7 +79,9 @@ class Migration : RealmMigration { .addField("real_name", String::class.java) .addField("is_bot", Boolean::class.java) .addField("deleted", Boolean::class.java) + .addField("last_updated", Long::class.java) + val timestamp = System.currentTimeMillis() schema.get("CoffeeBrewingEvent") .addRealmObjectField("user", userSchema) .transform { brewingEvent -> @@ -100,7 +103,8 @@ class Migration : RealmMigration { .findFirst() } - brewingEvent.setObject("user", user!!) + user!!.setLong("last_updated", timestamp) + brewingEvent.setObject("user", user) } } } diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt index e4308eb..9c18d53 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt @@ -9,6 +9,14 @@ class UserListResponse { var members = listOf() } +fun List.isFreshEnough(maxStaleness: Long): Boolean { + val oldestAcceptedTimestamp = System.currentTimeMillis() - maxStaleness + val sorted = sortedByDescending(User::last_updated) + val freshestUser = sorted.first() + + return freshestUser.last_updated > oldestAcceptedTimestamp +} + open class User( @PrimaryKey open var id: String = "", @@ -16,7 +24,8 @@ open class User( open var profile: Profile = Profile(), open var real_name: String? = null, open var is_bot: Boolean = false, - open var deleted: Boolean = false + open var deleted: Boolean = false, + open var last_updated: Long = 0 ) : RealmObject(), Parcelable { companion object { @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { @@ -31,7 +40,8 @@ open class User( source.readParcelable(ClassLoader.getSystemClassLoader()), source.readString(), 1 == source.readInt(), - 1 == source.readInt() + 1 == source.readInt(), + source.readLong() ) override fun describeContents() = 0 @@ -43,5 +53,6 @@ open class User( dest?.writeString(real_name) dest?.writeInt((if (is_bot) 1 else 0)) dest?.writeInt((if (deleted) 1 else 0)) + dest?.writeLong(last_updated) } } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt index 633e53d..5e1dc90 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt @@ -20,8 +20,10 @@ import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.network.models.isFreshEnough import rx.Observable import rx.Scheduler +import java.util.concurrent.TimeUnit open class LoadUsersUseCase( var userRepository: UserRepository, @@ -29,10 +31,20 @@ open class LoadUsersUseCase( var subscriber: Scheduler, var observer: Scheduler ) { + val MAX_CACHE_STALENESS = TimeUnit.HOURS.toMillis(12) + fun execute(): Observable> { + val currentTime = System.currentTimeMillis() val cachedUsers = Observable.just(userRepository.getAll()) val networkUsers = slackApi.getUsers(BuildConfig.SLACK_AUTH_TOKEN) - .flatMap { Observable.just(it.members) } + .flatMap { userResponse -> + val usersWithTimestamp = userResponse.members.toMutableList() + usersWithTimestamp.forEach { + it.last_updated = currentTime + } + + Observable.just(usersWithTimestamp) + } .subscribeOn(subscriber) .observeOn(observer) .map { @@ -44,8 +56,9 @@ open class LoadUsersUseCase( return Observable .concat(cachedUsers, networkUsers) - // TODO: only take the first (which is cached users) if it's fresh enough. - .first { it.isNotEmpty() } + .first { + it.isNotEmpty() && it.isFreshEnough(MAX_CACHE_STALENESS) + } } private fun filterNonCompanyUsers(response: List): List { From ed397a67a9654de845cae4b53518bf5f954504b8 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 20:33:58 +0200 Subject: [PATCH 14/28] Renamed "first", "second" and "third" variables to something more descriptive. --- .../data/local/RealmMigrationTest.kt | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt index 0c97a3a..020f7ca 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt @@ -58,29 +58,29 @@ class RealmMigrationTest { val all = realm.where(CoffeeBrewingEvent::class.java).findAll() assertThat(all.size, equalTo(3)) - val first = all[0] - assertThat(first.id, equalTo("adf9c9b9-e521-462f-9d67-ff2a11d7b62c")) - assertThat(first.time, equalTo(1485872637115L)) - assertThat(first.isSuccessful, equalTo(true)) - assertThat(first.user, nullValue()) + val brewingEventWithoutUserId = all[0] + assertThat(brewingEventWithoutUserId.id, equalTo("adf9c9b9-e521-462f-9d67-ff2a11d7b62c")) + assertThat(brewingEventWithoutUserId.time, equalTo(1485872637115L)) + assertThat(brewingEventWithoutUserId.isSuccessful, equalTo(true)) + assertThat(brewingEventWithoutUserId.user, nullValue()) - val second = all[1] - assertThat(second.id, equalTo("0e742762-7181-4bc0-b7b5-d1ff68991dd6")) - assertThat(second.time, equalTo(1485872637117L)) - assertThat(second.isSuccessful, equalTo(true)) - assertThat(second.user!!.id, equalTo("abc-123")) - assertThat(second.user!!.last_updated, not(equalTo(0L))) + val brewingEventWithUserId = all[1] + assertThat(brewingEventWithUserId.id, equalTo("0e742762-7181-4bc0-b7b5-d1ff68991dd6")) + assertThat(brewingEventWithUserId.time, equalTo(1485872637117L)) + assertThat(brewingEventWithUserId.isSuccessful, equalTo(true)) + assertThat(brewingEventWithUserId.user!!.id, equalTo("abc-123")) + assertThat(brewingEventWithUserId.user!!.last_updated, not(equalTo(0L))) - val third = all[2] - assertThat(third.id, equalTo("480bb3b9-a01f-45cb-87cd-113465d4038a")) - assertThat(third.time, equalTo(1485872637118L)) - assertThat(third.isSuccessful, equalTo(false)) - assertThat(third.user!!.id, equalTo("abc-123")) - assertThat(second.user!!.last_updated, not(equalTo(0L))) + val brewingAccident = all[2] + assertThat(brewingAccident.id, equalTo("480bb3b9-a01f-45cb-87cd-113465d4038a")) + assertThat(brewingAccident.time, equalTo(1485872637118L)) + assertThat(brewingAccident.isSuccessful, equalTo(false)) + assertThat(brewingAccident.user!!.id, equalTo("abc-123")) + assertThat(brewingEventWithUserId.user!!.last_updated, not(equalTo(0L))) // Make sure we don't generate different timestamps for the users in // this new schema. - assertThat(second.user!!.last_updated, equalTo(third.user!!.last_updated)) + assertThat(brewingEventWithUserId.user!!.last_updated, equalTo(brewingAccident.user!!.last_updated)) realm.close() } From c4f3b3f663f945fb7188703f607837d1de2297e3 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 20:34:34 +0200 Subject: [PATCH 15/28] Renamed the MigrationTest to reflect the name of the actual migration class. --- .../data/local/{RealmMigrationTest.kt => MigrationTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename app/src/androidTest/java/com/codemate/koffeemate/data/local/{RealmMigrationTest.kt => MigrationTest.kt} (99%) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt similarity index 99% rename from app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt rename to app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt index 020f7ca..bcc1318 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmMigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt @@ -30,7 +30,7 @@ import org.junit.Test import java.io.File import java.io.IOException -class RealmMigrationTest { +class MigrationTest { lateinit var context: Context @Before From 394368f3ab5ef4660400c7fb1b902c0b218f492c Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 20:50:21 +0200 Subject: [PATCH 16/28] Wrote some tests to ensure that the users are loaded from cache when appropriate. --- .../ui/userselector/LoadUsersUseCaseTest.kt | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt index ce0f872..8ae8d97 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt @@ -22,7 +22,7 @@ import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.SlackService import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.testutils.getResourceFile -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.hamcrest.core.IsEqual.equalTo @@ -30,10 +30,15 @@ import org.junit.After import org.junit.Assert.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations import rx.observers.TestSubscriber import rx.schedulers.Schedulers class LoadUsersUseCaseTest { + @Mock + lateinit var mockUserRepository: UserRepository + lateinit var mockServer: MockWebServer lateinit var slackApi: SlackApi lateinit var useCase: LoadUsersUseCase @@ -41,12 +46,14 @@ class LoadUsersUseCaseTest { @Before fun setUp() { + MockitoAnnotations.initMocks(this) + mockServer = MockWebServer() mockServer.start() slackApi = SlackService.getApi(mockServer.url("/")) useCase = LoadUsersUseCase( - mock(), + mockUserRepository, slackApi, Schedulers.immediate(), Schedulers.immediate() @@ -70,14 +77,62 @@ class LoadUsersUseCaseTest { } @Test - fun execute_WithValidData_CallsOnNextWithUsersAndCompletes() { - val userListJson = getResourceFile("seeds/sample_userlist_response.json").readText() - mockServer.enqueue(MockResponse().setBody(userListJson)) + fun execute_WhenHasNoCachedUsers_LoadsThemFromApi() { + enqueUserJsonResponseFromServer() + + useCase.execute().subscribe(testSubscriber) + testSubscriber.assertValueCount(1) + + val userList = testSubscriber.onNextEvents[0] + verifyUsersFromApi(userList) + } + + @Test + fun execute_WhenHasFreshEnoughCachedUsers_LoadsThemFromCache() { + val currentTime = System.currentTimeMillis() + val cachedUsers = listOf( + User(last_updated = currentTime), + User(last_updated = currentTime), + User(last_updated = currentTime) + ) + + whenever(mockUserRepository.getAll()).thenReturn(cachedUsers) + enqueUserJsonResponseFromServer() + + useCase.execute().subscribe(testSubscriber) + testSubscriber.assertValueCount(1) + + val userList = testSubscriber.onNextEvents[0] + assertThat(userList.size, equalTo(3)) + assertThat(userList, equalTo(cachedUsers)) + } + + @Test + fun execute_WhenHasTooOldCachedUsers_LoadsThemFromApi() { + val currentTime = System.currentTimeMillis() - useCase.MAX_CACHE_STALENESS + val cachedUsers = listOf( + User(last_updated = currentTime), + User(last_updated = currentTime), + User(last_updated = currentTime) + ) + + whenever(mockUserRepository.getAll()).thenReturn(cachedUsers) + enqueUserJsonResponseFromServer() useCase.execute().subscribe(testSubscriber) testSubscriber.assertValueCount(1) val userList = testSubscriber.onNextEvents[0] + verifyUsersFromApi(userList) + } + + // Utility functions --> + private fun enqueUserJsonResponseFromServer() { + val userListJson = getResourceFile("seeds/sample_userlist_response.json").readText() + mockServer.enqueue(MockResponse().setBody(userListJson)) + } + + private fun verifyUsersFromApi(userList: List) { assertThat(userList.size, equalTo(2)) with(userList[0]) { From 24924b30c821fc4c71f9d078ee2d05f2b9ff797c Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 20:53:06 +0200 Subject: [PATCH 17/28] Wrote a test for verifying that the users are cached every time when they're loaded from the API. --- .../ui/userselector/LoadUsersUseCaseTest.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt index 8ae8d97..877db91 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt @@ -22,6 +22,7 @@ import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.SlackService import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.testutils.getResourceFile +import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -76,6 +77,17 @@ class LoadUsersUseCaseTest { assertThat(request.path, equalTo("/users.list?token=${BuildConfig.SLACK_AUTH_TOKEN}")) } + @Test + fun execute_WhenLoadingUsersFromApi_CachesThem() { + enqueUserJsonResponseFromServer() + + useCase.execute().subscribe(testSubscriber) + testSubscriber.assertValueCount(1) + + val userList = testSubscriber.onNextEvents[0] + verify(mockUserRepository).addAll(userList) + } + @Test fun execute_WhenHasNoCachedUsers_LoadsThemFromApi() { enqueUserJsonResponseFromServer() From bdfd0d8c4752600eee18c65880ba6cc706d30076 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 21:03:48 +0200 Subject: [PATCH 18/28] Moved the model classes to one directory. --- .../data/local/CoffeeEventRepositoryTest.kt | 4 ++-- .../koffeemate/data/local/MigrationTest.kt | 2 +- .../data/local/UserRepositoryTest.kt | 2 +- .../data/local/CoffeeEventRepository.kt | 4 ++-- .../koffeemate/data/local/UserRepository.kt | 2 +- .../{local => }/models/CoffeeBrewingEvent.kt | 3 +-- .../data/{network => }/models/Profile.kt | 18 +++++++++++++++++- .../data/{network => }/models/User.kt | 18 +++++++++++++++++- .../koffeemate/data/network/SlackApi.kt | 3 +-- .../koffeemate/ui/main/MainActivity.kt | 4 ++-- .../koffeemate/ui/main/MainPresenter.kt | 2 +- .../codemate/koffeemate/ui/main/MainView.kt | 4 ++-- .../koffeemate/ui/main/PostAccidentUseCase.kt | 2 +- .../ui/userselector/LoadUsersUseCase.kt | 4 ++-- .../ui/userselector/UserSelectorActivity.kt | 2 +- .../ui/userselector/UserSelectorAdapter.kt | 2 +- .../ui/userselector/UserSelectorFragment.kt | 2 +- .../ui/userselector/UserSelectorPresenter.kt | 2 +- .../ui/userselector/UserSelectorView.kt | 2 +- .../koffeemate/testutils/CommonTestUtils.kt | 4 ++-- .../koffeemate/ui/main/MainPresenterTest.kt | 2 +- .../ui/userselector/LoadUsersUseCaseTest.kt | 2 +- .../userselector/UserSelectorPresenterTest.kt | 9 +-------- 23 files changed, 61 insertions(+), 38 deletions(-) rename app/src/main/java/com/codemate/koffeemate/data/{local => }/models/CoffeeBrewingEvent.kt (73%) rename app/src/main/java/com/codemate/koffeemate/data/{network => }/models/Profile.kt (72%) rename app/src/main/java/com/codemate/koffeemate/data/{network => }/models/User.kt (74%) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt index ead89d5..c6f07b2 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt @@ -16,8 +16,8 @@ package com.codemate.koffeemate.data.local -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.models.User import io.realm.Realm import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt index bcc1318..01c1399 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt @@ -18,7 +18,7 @@ package com.codemate.koffeemate.data.local import android.content.Context import android.support.test.InstrumentationRegistry -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.models.CoffeeBrewingEvent import io.realm.Realm import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt index ed993f5..33ccdbc 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt @@ -16,7 +16,7 @@ package com.codemate.koffeemate.data.local -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import io.realm.Realm import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt index fea2f46..4afadf7 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt @@ -1,7 +1,7 @@ package com.codemate.koffeemate.data.local -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.models.User import io.realm.Realm import io.realm.Sort import java.util.* diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt index 61b3d52..cdb83f9 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt @@ -16,7 +16,7 @@ package com.codemate.koffeemate.data.local -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import io.realm.Realm interface UserRepository { diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt b/app/src/main/java/com/codemate/koffeemate/data/models/CoffeeBrewingEvent.kt similarity index 73% rename from app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt rename to app/src/main/java/com/codemate/koffeemate/data/models/CoffeeBrewingEvent.kt index b456c66..e18f0bb 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/CoffeeBrewingEvent.kt @@ -1,6 +1,5 @@ -package com.codemate.koffeemate.data.local.models +package com.codemate.koffeemate.data.models -import com.codemate.koffeemate.data.network.models.User import io.realm.RealmObject import io.realm.annotations.PrimaryKey diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt similarity index 72% rename from app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt rename to app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt index 2f62308..afb6723 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt @@ -1,4 +1,20 @@ -package com.codemate.koffeemate.data.network.models +/* + * Copyright 2017 Codemate Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.codemate.koffeemate.data.models import android.os.Parcel import android.os.Parcelable diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/models/User.kt similarity index 74% rename from app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt rename to app/src/main/java/com/codemate/koffeemate/data/models/User.kt index 9c18d53..76d5178 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/User.kt @@ -1,4 +1,20 @@ -package com.codemate.koffeemate.data.network.models +/* + * Copyright 2017 Codemate Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.codemate.koffeemate.data.models import android.os.Parcel import android.os.Parcelable diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt b/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt index 9587d4a..d1d1c24 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt @@ -1,13 +1,12 @@ package com.codemate.koffeemate.data.network import com.codemate.koffeemate.BuildConfig -import com.codemate.koffeemate.data.network.models.UserListResponse +import com.codemate.koffeemate.data.models.UserListResponse import com.codemate.koffeemate.extensions.toRequestBody import okhttp3.HttpUrl import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.ResponseBody -import retrofit2.Call import retrofit2.Response import retrofit2.http.* import rx.Observable diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt index 42cbe86..3440297 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt @@ -10,8 +10,8 @@ import com.bumptech.glide.Glide import com.codemate.koffeemate.KoffeemateApp import com.codemate.koffeemate.R import com.codemate.koffeemate.common.ScreenSaver -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.di.modules.ActivityModule import com.codemate.koffeemate.extensions.loadBitmap import com.codemate.koffeemate.ui.settings.SettingsActivity diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt index 242ce69..a30f3d5 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt @@ -5,7 +5,7 @@ import com.codemate.koffeemate.common.BrewingProgressUpdater import com.codemate.koffeemate.common.ScreenSaver import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.ui.base.BasePresenter import okhttp3.ResponseBody import retrofit2.Response diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt index 2726134..803977e 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt @@ -1,7 +1,7 @@ package com.codemate.koffeemate.ui.main -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.ui.base.MvpView interface MainView : MvpView { diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt index 9f037fc..8a9298a 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt @@ -20,8 +20,8 @@ import android.graphics.Bitmap import com.codemate.koffeemate.common.AwardBadgeCreator import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.extensions.toRequestBody import okhttp3.MediaType import okhttp3.MultipartBody diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt index 5e1dc90..0b47fad 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt @@ -18,9 +18,9 @@ package com.codemate.koffeemate.ui.userselector import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.local.UserRepository +import com.codemate.koffeemate.data.models.User +import com.codemate.koffeemate.data.models.isFreshEnough import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.data.network.models.User -import com.codemate.koffeemate.data.network.models.isFreshEnough import rx.Observable import rx.Scheduler import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt index 232cee6..40240f9 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.MenuItem import com.codemate.koffeemate.R -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User class UserSelectorActivity : AppCompatActivity(), UserSelectorFragment.UserSelectListener { companion object { diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt index 4525bab..343a83e 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt @@ -6,7 +6,7 @@ import android.view.View import android.view.ViewGroup import com.bumptech.glide.Glide import com.codemate.koffeemate.R -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import kotlinx.android.synthetic.main.recycler_item_user.view.* import org.jetbrains.anko.onClick import java.util.* diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt index d4b9da2..b51aea6 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt @@ -28,7 +28,7 @@ import android.view.ViewGroup import com.codemate.koffeemate.KoffeemateApp import com.codemate.koffeemate.R import com.codemate.koffeemate.common.BasicListItemAnimator -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import kotlinx.android.synthetic.main.fragment_user_selector.* import kotlinx.android.synthetic.main.fragment_user_selector.view.* import org.jetbrains.anko.onClick diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt index c353dad..7f1a718 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt @@ -1,6 +1,6 @@ package com.codemate.koffeemate.ui.userselector -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.ui.base.BasePresenter import rx.Subscriber import javax.inject.Inject diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt index 0e4f05e..46b6858 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt @@ -1,6 +1,6 @@ package com.codemate.koffeemate.ui.userselector -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.ui.base.MvpView interface UserSelectorView : MvpView { diff --git a/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt b/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt index 3cda3eb..3cccba6 100644 --- a/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt +++ b/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt @@ -16,8 +16,8 @@ package com.codemate.koffeemate.testutils -import com.codemate.koffeemate.data.network.models.Profile -import com.codemate.koffeemate.data.network.models.User +import com.codemate.koffeemate.data.models.Profile +import com.codemate.koffeemate.data.models.User import java.io.File fun Any.getResourceFile(path: String): File { diff --git a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt index 516fa12..2d3a788 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt @@ -8,7 +8,7 @@ import com.codemate.koffeemate.common.BrewingProgressUpdater import com.codemate.koffeemate.common.ScreenSaver import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences -import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent +import com.codemate.koffeemate.data.models.CoffeeBrewingEvent import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt index 877db91..a834258 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt @@ -18,9 +18,9 @@ package com.codemate.koffeemate.ui.userselector import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.local.UserRepository +import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.SlackService -import com.codemate.koffeemate.data.network.models.User import com.codemate.koffeemate.testutils.getResourceFile import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt index 658d96a..8a52788 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt @@ -2,24 +2,17 @@ package com.codemate.koffeemate.ui.userselector import android.graphics.Bitmap import com.codemate.koffeemate.common.AwardBadgeCreator -import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences import com.codemate.koffeemate.data.local.UserRepository +import com.codemate.koffeemate.data.models.UserListResponse import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.data.network.models.Profile -import com.codemate.koffeemate.data.network.models.User -import com.codemate.koffeemate.data.network.models.UserListResponse import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile -import com.codemate.koffeemate.ui.main.PostAccidentUseCase import com.nhaarman.mockito_kotlin.* -import okhttp3.MediaType -import okhttp3.ResponseBody import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.MockitoAnnotations -import retrofit2.Response import rx.Observable import rx.schedulers.Schedulers From 0b9daabffc8a4205715a4b6d429c672d038043c1 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 1 Feb 2017 21:08:25 +0200 Subject: [PATCH 19/28] Moved the usecases to a separate package, so the ui package is purely UI only. --- .../java/com/codemate/koffeemate/di/modules/AppModule.kt | 6 +++--- .../java/com/codemate/koffeemate/ui/main/MainPresenter.kt | 2 ++ .../koffeemate/ui/userselector/UserSelectorPresenter.kt | 1 + .../{ui/userselector => usecases}/LoadUsersUseCase.kt | 2 +- .../koffeemate/{ui/main => usecases}/PostAccidentUseCase.kt | 2 +- .../{ui/main => usecases}/SendCoffeeAnnouncementUseCase.kt | 2 +- .../com/codemate/koffeemate/ui/main/MainPresenterTest.kt | 2 ++ .../koffeemate/ui/userselector/UserSelectorPresenterTest.kt | 1 + .../{ui/userselector => usecases}/LoadUsersUseCaseTest.kt | 2 +- .../userselector => usecases}/PostAccidentUseCaseTest.kt | 3 +-- .../main => usecases}/SendCoffeeAnnouncementUseCaseTest.kt | 3 +-- 11 files changed, 15 insertions(+), 11 deletions(-) rename app/src/main/java/com/codemate/koffeemate/{ui/userselector => usecases}/LoadUsersUseCase.kt (98%) rename app/src/main/java/com/codemate/koffeemate/{ui/main => usecases}/PostAccidentUseCase.kt (98%) rename app/src/main/java/com/codemate/koffeemate/{ui/main => usecases}/SendCoffeeAnnouncementUseCase.kt (96%) rename app/src/test/java/com/codemate/koffeemate/{ui/userselector => usecases}/LoadUsersUseCaseTest.kt (99%) rename app/src/test/java/com/codemate/koffeemate/{ui/userselector => usecases}/PostAccidentUseCaseTest.kt (97%) rename app/src/test/java/com/codemate/koffeemate/{ui/main => usecases}/SendCoffeeAnnouncementUseCaseTest.kt (97%) diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt index 269756a..d28eb12 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt @@ -25,9 +25,9 @@ import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.ui.userselector.LoadUsersUseCase -import com.codemate.koffeemate.ui.main.PostAccidentUseCase -import com.codemate.koffeemate.ui.main.SendCoffeeAnnouncementUseCase +import com.codemate.koffeemate.usecases.LoadUsersUseCase +import com.codemate.koffeemate.usecases.PostAccidentUseCase +import com.codemate.koffeemate.usecases.SendCoffeeAnnouncementUseCase import dagger.Module import dagger.Provides import rx.android.schedulers.AndroidSchedulers diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt index a30f3d5..8582ccd 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt @@ -7,6 +7,8 @@ import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.ui.base.BasePresenter +import com.codemate.koffeemate.usecases.PostAccidentUseCase +import com.codemate.koffeemate.usecases.SendCoffeeAnnouncementUseCase import okhttp3.ResponseBody import retrofit2.Response import rx.Subscriber diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt index 7f1a718..94106f7 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt @@ -2,6 +2,7 @@ package com.codemate.koffeemate.ui.userselector import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.ui.base.BasePresenter +import com.codemate.koffeemate.usecases.LoadUsersUseCase import rx.Subscriber import javax.inject.Inject diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt similarity index 98% rename from app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt rename to app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt index 0b47fad..cba4988 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.codemate.koffeemate.ui.userselector +package com.codemate.koffeemate.usecases import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.local.UserRepository diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt similarity index 98% rename from app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt rename to app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt index 8a9298a..86e3bfd 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.codemate.koffeemate.ui.main +package com.codemate.koffeemate.usecases import android.graphics.Bitmap import com.codemate.koffeemate.common.AwardBadgeCreator diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt similarity index 96% rename from app/src/main/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCase.kt rename to app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt index ad04100..c0d838e 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.codemate.koffeemate.ui.main +package com.codemate.koffeemate.usecases import com.codemate.koffeemate.data.network.SlackApi import okhttp3.ResponseBody diff --git a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt index 2d3a788..3682c44 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt @@ -12,6 +12,8 @@ import com.codemate.koffeemate.data.models.CoffeeBrewingEvent import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile +import com.codemate.koffeemate.usecases.PostAccidentUseCase +import com.codemate.koffeemate.usecases.SendCoffeeAnnouncementUseCase import com.nhaarman.mockito_kotlin.* import okhttp3.MediaType import okhttp3.ResponseBody diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt index 8a52788..3e9d3c7 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt @@ -8,6 +8,7 @@ import com.codemate.koffeemate.data.models.UserListResponse import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile +import com.codemate.koffeemate.usecases.LoadUsersUseCase import com.nhaarman.mockito_kotlin.* import org.junit.Before import org.junit.Test diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt similarity index 99% rename from app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt rename to app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt index a834258..7806d93 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.codemate.koffeemate.ui.userselector +package com.codemate.koffeemate.usecases import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.local.UserRepository diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt similarity index 97% rename from app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt rename to app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt index 20d3a60..cd9bac0 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.codemate.koffeemate.ui.userselector +package com.codemate.koffeemate.usecases import android.content.SharedPreferences import android.graphics.Bitmap @@ -27,7 +27,6 @@ import com.codemate.koffeemate.data.network.SlackService import com.codemate.koffeemate.testutils.RegexMatcher.Companion.matchesPattern import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile -import com.codemate.koffeemate.ui.main.PostAccidentUseCase import com.nhaarman.mockito_kotlin.* import okhttp3.ResponseBody import okhttp3.mockwebserver.MockResponse diff --git a/app/src/test/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt similarity index 97% rename from app/src/test/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCaseTest.kt rename to app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt index a7889a7..00c89bf 100644 --- a/app/src/test/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt @@ -14,12 +14,11 @@ * limitations under the License. */ -package com.codemate.koffeemate.ui.main +package com.codemate.koffeemate.usecases import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.network.SlackApi import com.codemate.koffeemate.data.network.SlackService -import okhttp3.Dispatcher import okhttp3.ResponseBody import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer From 28aedab2a01b4386a4f6a7e96593d9c95f49d939 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Thu, 2 Feb 2017 18:03:49 +0200 Subject: [PATCH 20/28] Fixed a brainfart. The "last_updated" for every migrated user should actually be zero, since the users are missing all data. --- .../java/com/codemate/koffeemate/data/local/MigrationTest.kt | 5 ++--- .../java/com/codemate/koffeemate/data/local/Migration.kt | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt index 01c1399..67cb152 100644 --- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt +++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt @@ -22,7 +22,6 @@ import com.codemate.koffeemate.data.models.CoffeeBrewingEvent import io.realm.Realm import io.realm.RealmConfiguration import org.hamcrest.core.IsEqual.equalTo -import org.hamcrest.core.IsNot.not import org.hamcrest.core.IsNull.nullValue import org.junit.Assert.assertThat import org.junit.Before @@ -69,14 +68,14 @@ class MigrationTest { assertThat(brewingEventWithUserId.time, equalTo(1485872637117L)) assertThat(brewingEventWithUserId.isSuccessful, equalTo(true)) assertThat(brewingEventWithUserId.user!!.id, equalTo("abc-123")) - assertThat(brewingEventWithUserId.user!!.last_updated, not(equalTo(0L))) + assertThat(brewingEventWithUserId.user!!.last_updated, equalTo(0L)) val brewingAccident = all[2] assertThat(brewingAccident.id, equalTo("480bb3b9-a01f-45cb-87cd-113465d4038a")) assertThat(brewingAccident.time, equalTo(1485872637118L)) assertThat(brewingAccident.isSuccessful, equalTo(false)) assertThat(brewingAccident.user!!.id, equalTo("abc-123")) - assertThat(brewingEventWithUserId.user!!.last_updated, not(equalTo(0L))) + assertThat(brewingEventWithUserId.user!!.last_updated, equalTo(0L)) // Make sure we don't generate different timestamps for the users in // this new schema. diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt index f9ef371..a7045e7 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt @@ -81,7 +81,6 @@ class Migration : RealmMigration { .addField("deleted", Boolean::class.java) .addField("last_updated", Long::class.java) - val timestamp = System.currentTimeMillis() schema.get("CoffeeBrewingEvent") .addRealmObjectField("user", userSchema) .transform { brewingEvent -> @@ -103,7 +102,6 @@ class Migration : RealmMigration { .findFirst() } - user!!.setLong("last_updated", timestamp) brewingEvent.setObject("user", user) } } From bfb272b5cbb13266055d6f8f0da98bca44f5d07a Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Thu, 2 Feb 2017 18:05:19 +0200 Subject: [PATCH 21/28] Fixed a potential NPE waiting to happen, when the user doesn't happen to have a profile image. --- .../koffeemate/data/models/Profile.kt | 40 +++++++++---------- .../ui/userselector/UserSelectorAdapter.kt | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt index afb6723..7109d44 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt @@ -29,34 +29,34 @@ open class Profile( open var image_512: String? = null ) : RealmObject(), Parcelable { val largestAvailableImage: String - get() { - var imageUrl: String? = image_512 + get() { + var imageUrl: String? = image_512 - if (imageUrl.isNullOrBlank()) { - imageUrl = image_192 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_192 + } - if (imageUrl.isNullOrBlank()) { - imageUrl = image_72 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_72 + } - return imageUrl ?: "" - } + return imageUrl ?: "" + } val smallestAvailableImage: String - get() { - var imageUrl: String? = image_72 + get() { + var imageUrl: String? = image_72 - if (imageUrl.isNullOrBlank()) { - imageUrl = image_192 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_192 + } - if (imageUrl.isNullOrBlank()) { - imageUrl = image_512 - } + if (imageUrl.isNullOrBlank()) { + imageUrl = image_512 + } - return imageUrl ?: "" - } + return imageUrl ?: "" + } companion object { @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt index 343a83e..1e414ac 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt @@ -37,7 +37,7 @@ class UserSelectorAdapter(val onUserSelectedListener: (user: User) -> Unit) : inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(user: User) = with(itemView) { Glide.with(context) - .load(user.profile.image_72) + .load(user.profile.smallestAvailableImage) .into(profileImage) userName.text = user.profile.real_name From 600f5389e8030b07ad70fa722aa2e917c0615046 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 15:58:48 +0200 Subject: [PATCH 22/28] Reduced the largest- and smallestAvailableImage field getters to two lines each in the Profile class. --- .../koffeemate/data/models/Profile.kt | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt index 7109d44..39dab82 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt @@ -29,34 +29,10 @@ open class Profile( open var image_512: String? = null ) : RealmObject(), Parcelable { val largestAvailableImage: String - get() { - var imageUrl: String? = image_512 - - if (imageUrl.isNullOrBlank()) { - imageUrl = image_192 - } - - if (imageUrl.isNullOrBlank()) { - imageUrl = image_72 - } - - return imageUrl ?: "" - } + get() = image_512 ?: image_192 ?: image_72 ?: "" val smallestAvailableImage: String - get() { - var imageUrl: String? = image_72 - - if (imageUrl.isNullOrBlank()) { - imageUrl = image_192 - } - - if (imageUrl.isNullOrBlank()) { - imageUrl = image_512 - } - - return imageUrl ?: "" - } + get() = image_72 ?: image_192 ?: image_512 ?: "" companion object { @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { From 3b797f951e708335baf231f4d85ee7b81212639f Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 16:01:57 +0200 Subject: [PATCH 23/28] Removed SlackService class and moved the API creation to the SlackApi class. --- .../koffeemate/data/network/SlackApi.kt | 13 +++++++ .../koffeemate/data/network/SlackService.kt | 34 ------------------- .../koffeemate/di/modules/NetModule.kt | 4 +-- .../usecases/LoadUsersUseCaseTest.kt | 3 +- .../usecases/PostAccidentUseCaseTest.kt | 3 +- .../SendCoffeeAnnouncementUseCaseTest.kt | 3 +- 6 files changed, 18 insertions(+), 42 deletions(-) delete mode 100644 app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt b/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt index d1d1c24..91803e9 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt @@ -8,6 +8,9 @@ import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.ResponseBody import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.* import rx.Observable @@ -39,5 +42,15 @@ interface SlackApi { companion object { val BASE_URL = HttpUrl.parse("https://slack.com/api/")!! + + fun create(baseUrl: HttpUrl): SlackApi { + val retrofit = Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build() + + return retrofit.create(SlackApi::class.java) + } } } diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt b/app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt deleted file mode 100644 index 7302767..0000000 --- a/app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2016 Codemate Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.codemate.koffeemate.data.network - -import okhttp3.HttpUrl -import retrofit2.Retrofit -import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory - -object SlackService { - fun getApi(baseUrl: HttpUrl): SlackApi { - val retrofit = Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .build() - - return retrofit.create(SlackApi::class.java) - } -} diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt index 4b86b84..0a70494 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt @@ -16,7 +16,7 @@ package com.codemate.koffeemate.di.modules -import com.codemate.koffeemate.data.network.SlackService +import com.codemate.koffeemate.data.network.SlackApi import dagger.Module import dagger.Provides import okhttp3.HttpUrl @@ -26,5 +26,5 @@ import javax.inject.Singleton class NetModule(val baseUrl: HttpUrl) { @Provides @Singleton - fun provideApi() = SlackService.getApi(baseUrl) + fun provideApi() = SlackApi.create(baseUrl) } \ No newline at end of file diff --git a/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt index 7806d93..9ad462e 100644 --- a/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt @@ -20,7 +20,6 @@ import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.local.UserRepository import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.data.network.SlackService import com.codemate.koffeemate.testutils.getResourceFile import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever @@ -52,7 +51,7 @@ class LoadUsersUseCaseTest { mockServer = MockWebServer() mockServer.start() - slackApi = SlackService.getApi(mockServer.url("/")) + slackApi = SlackApi.create(mockServer.url("/")) useCase = LoadUsersUseCase( mockUserRepository, slackApi, diff --git a/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt index cd9bac0..3e35ed5 100644 --- a/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt @@ -23,7 +23,6 @@ import com.codemate.koffeemate.common.AwardBadgeCreator import com.codemate.koffeemate.data.local.CoffeeEventRepository import com.codemate.koffeemate.data.local.CoffeePreferences import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.data.network.SlackService import com.codemate.koffeemate.testutils.RegexMatcher.Companion.matchesPattern import com.codemate.koffeemate.testutils.fakeUser import com.codemate.koffeemate.testutils.getResourceFile @@ -75,7 +74,7 @@ class PostAccidentUseCaseTest { whenever(mockAwardBadgeCreator.createBitmapFileWithAward(mockBitmap, 1)) .thenReturn(getResourceFile("images/empty.png")) - slackApi = SlackService.getApi(mockServer.url("/")) + slackApi = SlackApi.create(mockServer.url("/")) useCase = PostAccidentUseCase( slackApi, mockCoffeeEventRepository, diff --git a/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt index 00c89bf..915b689 100644 --- a/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt +++ b/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt @@ -18,7 +18,6 @@ package com.codemate.koffeemate.usecases import com.codemate.koffeemate.BuildConfig import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.data.network.SlackService import okhttp3.ResponseBody import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -48,7 +47,7 @@ class SendCoffeeAnnouncementUseCaseTest { mockServer = MockWebServer() mockServer.start() - slackApi = SlackService.getApi(mockServer.url("/")) + slackApi = SlackApi.create(mockServer.url("/")) useCase = SendCoffeeAnnouncementUseCase( slackApi, Schedulers.immediate(), From 9efd239523439a8cf09e112fec534ea1ab9a2255 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 16:16:34 +0200 Subject: [PATCH 24/28] Removed unnecessary manual instantiation of usecases, because Dagger can do that for us. --- .../koffeemate/di/components/AppComponent.kt | 8 ++-- .../koffeemate/di/modules/AppModule.kt | 47 ------------------- .../koffeemate/di/modules/ThreadingModule.kt | 35 ++++++++++++++ .../koffeemate/usecases/LoadUsersUseCase.kt | 8 ++-- .../usecases/PostAccidentUseCase.kt | 8 ++-- .../usecases/SendCoffeeAnnouncementUseCase.kt | 8 ++-- 6 files changed, 53 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt diff --git a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt index 50bb9b4..2bb6c0e 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt @@ -16,10 +16,7 @@ package com.codemate.koffeemate.di.components -import com.codemate.koffeemate.di.modules.ActivityModule -import com.codemate.koffeemate.di.modules.AppModule -import com.codemate.koffeemate.di.modules.NetModule -import com.codemate.koffeemate.di.modules.PersistenceModule +import com.codemate.koffeemate.di.modules.* import com.codemate.koffeemate.ui.userselector.UserSelectorActivity import com.codemate.koffeemate.ui.userselector.UserSelectorFragment import dagger.Component @@ -29,7 +26,8 @@ import javax.inject.Singleton @Component(modules = arrayOf( AppModule::class, PersistenceModule::class, - NetModule::class) + NetModule::class, + ThreadingModule::class) ) interface AppComponent { fun inject(userSelectorFragment: UserSelectorFragment) diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt index d28eb12..d25e57f 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt @@ -21,17 +21,8 @@ import com.codemate.koffeemate.KoffeemateApp import com.codemate.koffeemate.common.AndroidAwardBadgeCreator import com.codemate.koffeemate.common.AwardBadgeCreator import com.codemate.koffeemate.common.BrewingProgressUpdater -import com.codemate.koffeemate.data.local.CoffeeEventRepository -import com.codemate.koffeemate.data.local.CoffeePreferences -import com.codemate.koffeemate.data.local.UserRepository -import com.codemate.koffeemate.data.network.SlackApi -import com.codemate.koffeemate.usecases.LoadUsersUseCase -import com.codemate.koffeemate.usecases.PostAccidentUseCase -import com.codemate.koffeemate.usecases.SendCoffeeAnnouncementUseCase import dagger.Module import dagger.Provides -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -52,42 +43,4 @@ class AppModule(val app: KoffeemateApp) { @Provides @Singleton fun provideAwardBadgeCreator(ctx: Context): AwardBadgeCreator = AndroidAwardBadgeCreator(ctx) - - /** - * Move these to a better place once you actually understand Dagger ¯\_(ツ)_/¯ - */ - @Provides - @Singleton - fun provideSendCoffeeAnnouncementUseCase(slackApi: SlackApi) = - SendCoffeeAnnouncementUseCase( - slackApi, - Schedulers.newThread(), - AndroidSchedulers.mainThread() - ) - - @Provides - @Singleton - fun provideLoadUsersUseCase(userRepository: UserRepository, slackApi: SlackApi) = - LoadUsersUseCase( - userRepository, - slackApi, - Schedulers.newThread(), - AndroidSchedulers.mainThread() - ) - - @Provides - @Singleton - fun providePostAccidentUseCase( - slackApi: SlackApi, - coffeeEventRepository: CoffeeEventRepository, - coffeePreferences: CoffeePreferences, - awardBadgeCreator: AwardBadgeCreator - ) = PostAccidentUseCase( - slackApi, - coffeeEventRepository, - coffeePreferences, - awardBadgeCreator, - Schedulers.newThread(), - AndroidSchedulers.mainThread() - ) } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt new file mode 100644 index 0000000..7e11cfb --- /dev/null +++ b/app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Codemate Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.codemate.koffeemate.di.modules + +import dagger.Module +import dagger.Provides +import rx.Scheduler +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import javax.inject.Named + +@Module +class ThreadingModule { + @Provides + @Named("subscriber") + fun provideSubscriber(): Scheduler = Schedulers.newThread() + + @Provides + @Named("observer") + fun provideObserver(): Scheduler = AndroidSchedulers.mainThread() +} \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt index cba4988..48fa8bc 100644 --- a/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt @@ -24,12 +24,14 @@ import com.codemate.koffeemate.data.network.SlackApi import rx.Observable import rx.Scheduler import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Named -open class LoadUsersUseCase( +open class LoadUsersUseCase @Inject constructor( var userRepository: UserRepository, var slackApi: SlackApi, - var subscriber: Scheduler, - var observer: Scheduler + @Named("subscriber") var subscriber: Scheduler, + @Named("observer") var observer: Scheduler ) { val MAX_CACHE_STALENESS = TimeUnit.HOURS.toMillis(12) diff --git a/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt index 86e3bfd..aa23397 100644 --- a/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt @@ -30,14 +30,16 @@ import okhttp3.ResponseBody import retrofit2.Response import rx.Observable import rx.Scheduler +import javax.inject.Inject +import javax.inject.Named -open class PostAccidentUseCase( +open class PostAccidentUseCase @Inject constructor( var slackApi: SlackApi, val coffeeEventRepository: CoffeeEventRepository, val coffeePreferences: CoffeePreferences, val awardBadgeCreator: AwardBadgeCreator, - var subscriber: Scheduler, - var observer: Scheduler + @Named("subscriber") var subscriber: Scheduler, + @Named("observer") var observer: Scheduler ) { fun execute( comment: String, diff --git a/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt index c0d838e..6e94b48 100644 --- a/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt +++ b/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt @@ -21,11 +21,13 @@ import okhttp3.ResponseBody import retrofit2.Response import rx.Observable import rx.Scheduler +import javax.inject.Inject +import javax.inject.Named -open class SendCoffeeAnnouncementUseCase( +open class SendCoffeeAnnouncementUseCase @Inject constructor( var slackApi: SlackApi, - var subscriber: Scheduler, - var observer: Scheduler + @Named("subscriber") var subscriber: Scheduler, + @Named("observer") var observer: Scheduler ) { fun execute(channel: String, newCoffeeMessage: String): Observable> { return slackApi.postMessage(channel, newCoffeeMessage) From a9dba03cbe6a761cab0ca41f2b529b0f361bfe51 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 19:52:39 +0200 Subject: [PATCH 25/28] Now all Realm instances are closed after use. --- .../data/local/CoffeeEventRepository.kt | 31 ++++++++++++------- .../koffeemate/data/local/UserRepository.kt | 9 ++++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt index 4afadf7..7251845 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt @@ -44,27 +44,34 @@ class RealmCoffeeEventRepository : CoffeeEventRepository { return@with event!! } - override fun getAccidentCountForUser(user: User) = - Realm.getDefaultInstance() - .where(CoffeeBrewingEvent::class.java) - .equalTo("isSuccessful", false) - .equalTo("user.id", user.id) - .count() + override fun getAccidentCountForUser(user: User) = with(Realm.getDefaultInstance()) { + val count = where(CoffeeBrewingEvent::class.java) + .equalTo("isSuccessful", false) + .equalTo("user.id", user.id) + .count() + + close() + return@with count + } - override fun getLastBrewingEvent(): CoffeeBrewingEvent? { - return Realm.getDefaultInstance() - .where(CoffeeBrewingEvent::class.java) + override fun getLastBrewingEvent() = with(Realm.getDefaultInstance()) { + val lastEvent = where(CoffeeBrewingEvent::class.java) .equalTo("isSuccessful", true) .findAllSorted("time", Sort.ASCENDING) .lastOrNull() + + close() + return@with lastEvent } - override fun getLastBrewingAccident(): CoffeeBrewingEvent? { - return Realm.getDefaultInstance() - .where(CoffeeBrewingEvent::class.java) + override fun getLastBrewingAccident() = with(Realm.getDefaultInstance()) { + val lastAccident = where(CoffeeBrewingEvent::class.java) .equalTo("isSuccessful", false) .findAllSorted("time", Sort.ASCENDING) .lastOrNull() + + close() + return@with lastAccident } private fun newEvent(realm: Realm) = diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt index cdb83f9..2e04fdb 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt @@ -32,7 +32,10 @@ class RealmUserRepository : UserRepository { } } - override fun getAll(): List = Realm.getDefaultInstance() - .where(User::class.java) - .findAll() + override fun getAll(): List = with(Realm.getDefaultInstance()) { + val all = where(User::class.java).findAll() + close() + + return@with all + } } \ No newline at end of file From b229646770c01a595cb1720bb505a1312c096824 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 20:12:38 +0200 Subject: [PATCH 26/28] Refactored the accident reports, enabling to get us rid of the ugly Parcelable stuff that didn't even work. --- app/src/main/AndroidManifest.xml | 4 -- .../koffeemate/data/models/Profile.kt | 25 +---------- .../codemate/koffeemate/data/models/User.kt | 33 +------------- .../koffeemate/di/components/AppComponent.kt | 1 - .../koffeemate/ui/main/MainActivity.kt | 44 +++++++++---------- .../ui/userselector/UserSelectorActivity.kt | 40 ----------------- .../ui/userselector/UserSelectorFragment.kt | 22 +++++++--- app/src/main/res/values/strings.xml | 2 +- 8 files changed, 42 insertions(+), 129 deletions(-) delete mode 100644 app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b342fd..fdb0e95 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,10 +21,6 @@ - - diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt index 7109d44..93d35f0 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt @@ -16,8 +16,6 @@ package com.codemate.koffeemate.data.models -import android.os.Parcel -import android.os.Parcelable import io.realm.RealmObject open class Profile( @@ -27,7 +25,7 @@ open class Profile( open var image_72: String? = null, open var image_192: String? = null, open var image_512: String? = null -) : RealmObject(), Parcelable { +) : RealmObject() { val largestAvailableImage: String get() { var imageUrl: String? = image_512 @@ -57,25 +55,4 @@ open class Profile( return imageUrl ?: "" } - - companion object { - @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): Profile = Profile(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } - - constructor(source: Parcel) : this( - source.readString(), - source.readString(), - source.readString() - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel?, flags: Int) { - dest?.writeString(first_name) - dest?.writeString(last_name) - dest?.writeString(real_name) - } } diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/models/User.kt index 76d5178..1bc81d9 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/models/User.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/User.kt @@ -16,8 +16,6 @@ package com.codemate.koffeemate.data.models -import android.os.Parcel -import android.os.Parcelable import io.realm.RealmObject import io.realm.annotations.PrimaryKey @@ -42,33 +40,4 @@ open class User( open var is_bot: Boolean = false, open var deleted: Boolean = false, open var last_updated: Long = 0 -) : RealmObject(), Parcelable { - companion object { - @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): User = User(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } - - constructor(source: Parcel) : this( - source.readString(), - source.readString(), - source.readParcelable(ClassLoader.getSystemClassLoader()), - source.readString(), - 1 == source.readInt(), - 1 == source.readInt(), - source.readLong() - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel?, flags: Int) { - dest?.writeString(id) - dest?.writeString(name) - dest?.writeParcelable(profile, 0) - dest?.writeString(real_name) - dest?.writeInt((if (is_bot) 1 else 0)) - dest?.writeInt((if (deleted) 1 else 0)) - dest?.writeLong(last_updated) - } -} \ No newline at end of file +) : RealmObject() \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt index 50bb9b4..1dfdfdc 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt @@ -20,7 +20,6 @@ import com.codemate.koffeemate.di.modules.ActivityModule import com.codemate.koffeemate.di.modules.AppModule import com.codemate.koffeemate.di.modules.NetModule import com.codemate.koffeemate.di.modules.PersistenceModule -import com.codemate.koffeemate.ui.userselector.UserSelectorActivity import com.codemate.koffeemate.ui.userselector.UserSelectorFragment import dagger.Component import javax.inject.Singleton diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt index 3440297..4e53164 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt @@ -1,7 +1,6 @@ package com.codemate.koffeemate.ui.main import android.app.ProgressDialog -import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.View @@ -15,7 +14,6 @@ import com.codemate.koffeemate.data.models.User import com.codemate.koffeemate.di.modules.ActivityModule import com.codemate.koffeemate.extensions.loadBitmap import com.codemate.koffeemate.ui.settings.SettingsActivity -import com.codemate.koffeemate.ui.userselector.UserSelectorActivity import com.codemate.koffeemate.ui.userselector.UserSelectorFragment import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.view_coffee_progress.view.* @@ -23,7 +21,8 @@ import org.jetbrains.anko.* import javax.inject.Inject class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSelectListener { - private val REQUEST_CODE_SHAME_USER = 1 + private val REQUEST_WHOS_BREWING = 1 + private val REQUEST_WHO_FAILED_BREWING = 2 @Inject lateinit var presenter: MainPresenter @@ -91,20 +90,28 @@ class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSel // Functions for identifying who brews the coffee override fun selectCoffeeBrewingPerson() { - UserSelectorFragment - .newInstance() - .show(supportFragmentManager, "user_selector") + UserSelectorFragment.newInstance( + title = getString(R.string.prompt_select_person_below), + requestCode = REQUEST_WHOS_BREWING + ).show(supportFragmentManager, "user_selector") } override fun clearCoffeeBrewingPerson() { coffeeProgressView.userSetterButton.clearUser() } - override fun onUserSelected(user: User) { - Glide.with(this) - .load(user.profile.smallestAvailableImage) - .into(coffeeProgressView.userSetterButton) - presenter.personBrewingCoffee = user + override fun onUserSelected(user: User, requestCode: Int) { + when (requestCode) { + REQUEST_WHOS_BREWING -> { + Glide.with(this) + .load(user.profile.smallestAvailableImage) + .into(coffeeProgressView.userSetterButton) + presenter.personBrewingCoffee = user + } + REQUEST_WHO_FAILED_BREWING -> { + showPostAccidentAnnouncementPrompt(user) + } + } } // MainView methods --> @@ -156,17 +163,10 @@ class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSel // Shaming users for coffee brewing failures --> override fun launchUserSelector() { - startActivityForResult( - intentFor(), - REQUEST_CODE_SHAME_USER - ) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE_SHAME_USER && resultCode == RESULT_OK && data != null) { - val user = data.getParcelableExtra(UserSelectorActivity.RESULT_USER) - showPostAccidentAnnouncementPrompt(user) - } + UserSelectorFragment.newInstance( + title = getString(R.string.prompt_who_is_guilty), + requestCode = REQUEST_WHO_FAILED_BREWING + ).show(supportFragmentManager, "user_selector") } override fun showPostAccidentAnnouncementPrompt(user: User) { diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt deleted file mode 100644 index 40240f9..0000000 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.codemate.koffeemate.ui.userselector - -import android.content.Intent -import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.view.MenuItem -import com.codemate.koffeemate.R -import com.codemate.koffeemate.data.models.User - -class UserSelectorActivity : AppCompatActivity(), UserSelectorFragment.UserSelectListener { - companion object { - val RESULT_USER = "user" - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - supportFragmentManager.beginTransaction() - .replace(android.R.id.content, UserSelectorFragment.newInstance()) - .commit() - - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setTitle(R.string.prompt_select_guilty_person) - } - - override fun onUserSelected(user: User) { - val intent = Intent() - intent.putExtra(RESULT_USER, user) - setResult(RESULT_OK, intent) - finish() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - finish() - } - - return super.onOptionsItemSelected(item) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt index b51aea6..3354ea9 100644 --- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt +++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt @@ -38,12 +38,22 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView { private lateinit var userSelectorAdapter: UserSelectorAdapter private lateinit var userSelectListener: UserSelectListener + private var requestCode: Int = 0 + @Inject lateinit var presenter: UserSelectorPresenter companion object { - fun newInstance(): UserSelectorFragment { + private val ARG_TITLE = "title" + private val ARG_REQUEST_CODE = "request_code" + + fun newInstance(title: String, requestCode: Int): UserSelectorFragment { + val args = Bundle() + args.putString(ARG_TITLE, title) + args.putInt(ARG_REQUEST_CODE, requestCode) + val fragment = UserSelectorFragment() + fragment.arguments = args fragment.setStyle(DialogFragment.STYLE_NORMAL, R.style.TitledDialog) return fragment @@ -51,7 +61,7 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView { } interface UserSelectListener { - fun onUserSelected(user: User) + fun onUserSelected(user: User, requestCode: Int) } @Suppress("UNCHECKED_CAST") @@ -72,8 +82,9 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView { override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - KoffeemateApp.appComponent.inject(this) + + requestCode = arguments.getInt(ARG_REQUEST_CODE) setUpUserRecycler() presenter.attachView(this) @@ -86,14 +97,15 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) - dialog.setTitle(R.string.prompt_select_person_below) + val title = arguments.getString(ARG_TITLE) + dialog.setTitle(title) return dialog } private fun setUpUserRecycler() { userSelectorAdapter = UserSelectorAdapter { user -> - userSelectListener.onUserSelected(user) + userSelectListener.onUserSelected(user, requestCode) dismiss() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 460093f..12462d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,7 +63,7 @@ Reset the counter? - Who is guilty of this coffee brewing accident? + Who failed coffee brewing? Who is brewing? Select a guilty person From 7b92f3c0180cecb4d2f5a1bc9fc39d5fc6056683 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 20:14:23 +0200 Subject: [PATCH 27/28] Got rid of the Parcelable stuff again, because I seem to be such a noob with merges. --- .../koffeemate/data/models/Profile.kt | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt index 39dab82..0329c3b 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt @@ -16,8 +16,6 @@ package com.codemate.koffeemate.data.models -import android.os.Parcel -import android.os.Parcelable import io.realm.RealmObject open class Profile( @@ -27,31 +25,10 @@ open class Profile( open var image_72: String? = null, open var image_192: String? = null, open var image_512: String? = null -) : RealmObject(), Parcelable { +) : RealmObject() { val largestAvailableImage: String get() = image_512 ?: image_192 ?: image_72 ?: "" val smallestAvailableImage: String get() = image_72 ?: image_192 ?: image_512 ?: "" - - companion object { - @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): Profile = Profile(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } - - constructor(source: Parcel) : this( - source.readString(), - source.readString(), - source.readString() - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel?, flags: Int) { - dest?.writeString(first_name) - dest?.writeString(last_name) - dest?.writeString(real_name) - } } From 347c7fd7b73bb4dfa9d2912e87e13c195d17322b Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Fri, 3 Feb 2017 20:35:12 +0200 Subject: [PATCH 28/28] Copied the user results from Realm to an unmanaged list, since the RealmResults are unusable if the instance gets closed. --- .../com/codemate/koffeemate/data/local/UserRepository.kt | 5 +++-- .../com/codemate/koffeemate/di/components/AppComponent.kt | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt index 2e04fdb..79c7862 100644 --- a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt +++ b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt @@ -34,8 +34,9 @@ class RealmUserRepository : UserRepository { override fun getAll(): List = with(Realm.getDefaultInstance()) { val all = where(User::class.java).findAll() - close() + val copy = copyFromRealm(all) - return@with all + close() + return@with copy } } \ No newline at end of file diff --git a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt index 2bb6c0e..63626a9 100644 --- a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt +++ b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt @@ -17,7 +17,6 @@ package com.codemate.koffeemate.di.components import com.codemate.koffeemate.di.modules.* -import com.codemate.koffeemate.ui.userselector.UserSelectorActivity import com.codemate.koffeemate.ui.userselector.UserSelectorFragment import dagger.Component import javax.inject.Singleton