mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Migrate project to Kotlin Multiplatform (KMP) architecture (#4738)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
182ad933f4
commit
0ce322a0f5
163 changed files with 1837 additions and 877 deletions
|
|
@ -95,7 +95,6 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -161,7 +160,7 @@ private const val ROUNDED_CORNER_PERCENT = 100
|
|||
fun MessageScreen(
|
||||
contactKey: String,
|
||||
message: String,
|
||||
viewModel: MessageViewModel = hiltViewModel(),
|
||||
viewModel: MessageViewModel,
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
navigateToQuickChatOptions: () -> Unit,
|
||||
onNavigateBack: () -> Unit,
|
||||
|
|
@ -61,7 +61,6 @@ import androidx.compose.ui.focus.onFocusEvent
|
|||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
|
|
@ -85,11 +84,7 @@ import org.meshtastic.core.ui.component.rememberDragDropState
|
|||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun QuickChatScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: QuickChatViewModel = hiltViewModel(),
|
||||
onNavigateUp: () -> Unit,
|
||||
) {
|
||||
fun QuickChatScreen(modifier: Modifier = Modifier, viewModel: QuickChatViewModel, onNavigateUp: () -> Unit) {
|
||||
val actions by viewModel.quickChatActions.collectAsStateWithLifecycle()
|
||||
var showActionDialog by remember { mutableStateOf<QuickChatAction?>(null) }
|
||||
|
||||
|
|
@ -64,6 +64,8 @@ import org.meshtastic.proto.SharedContact
|
|||
@Composable
|
||||
fun AdaptiveContactsScreen(
|
||||
navController: NavHostController,
|
||||
contactsViewModel: org.meshtastic.feature.messaging.ui.contact.ContactsViewModel,
|
||||
messageViewModel: org.meshtastic.feature.messaging.MessageViewModel,
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent>,
|
||||
sharedContactRequested: SharedContact?,
|
||||
requestChannelSet: ChannelSet?,
|
||||
|
|
@ -138,6 +140,7 @@ fun AdaptiveContactsScreen(
|
|||
onHandleScannedUri = onHandleScannedUri,
|
||||
onClearSharedContactRequested = onClearSharedContactRequested,
|
||||
onClearRequestChannelUrl = onClearRequestChannelUrl,
|
||||
viewModel = contactsViewModel,
|
||||
onClickNodeChip = {
|
||||
navController.navigate(NodesRoutes.NodeDetailGraph(it)) {
|
||||
launchSingleTop = true
|
||||
|
|
@ -160,6 +163,7 @@ fun AdaptiveContactsScreen(
|
|||
MessageScreen(
|
||||
contactKey = contactKey,
|
||||
message = if (contactKey == initialContactKey) initialMessage else "",
|
||||
viewModel = messageViewModel,
|
||||
navigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) },
|
||||
navigateToQuickChatOptions = { navController.navigate(ContactsRoutes.QuickChat) },
|
||||
onNavigateBack = handleBack,
|
||||
|
|
@ -53,7 +53,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
|
|
@ -121,7 +120,7 @@ fun ContactsScreen(
|
|||
onHandleScannedUri: (Uri, onInvalid: () -> Unit) -> Unit,
|
||||
onClearSharedContactRequested: () -> Unit,
|
||||
onClearRequestChannelUrl: () -> Unit,
|
||||
viewModel: ContactsViewModel = hiltViewModel<ContactsViewModel>(),
|
||||
viewModel: ContactsViewModel,
|
||||
onClickNodeChip: (Int) -> Unit = {},
|
||||
onNavigateToMessages: (String) -> Unit = {},
|
||||
onNavigateToNodeDetails: (Int) -> Unit = {},
|
||||
|
|
@ -36,7 +36,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.Contact
|
||||
|
|
@ -52,7 +51,7 @@ import org.meshtastic.feature.messaging.ui.contact.ContactItem
|
|||
import org.meshtastic.feature.messaging.ui.contact.ContactsViewModel
|
||||
|
||||
@Composable
|
||||
fun ShareScreen(viewModel: ContactsViewModel = hiltViewModel(), onConfirm: (String) -> Unit, onNavigateUp: () -> Unit) {
|
||||
fun ShareScreen(viewModel: ContactsViewModel, onConfirm: (String) -> Unit, onNavigateUp: () -> Unit) {
|
||||
val contactList by viewModel.contactList.collectAsStateWithLifecycle()
|
||||
|
||||
ShareScreen(contacts = contactList, onConfirm = onConfirm, onNavigateUp = onNavigateUp)
|
||||
|
|
@ -21,7 +21,6 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -49,13 +48,9 @@ import org.meshtastic.core.repository.UiPrefs
|
|||
import org.meshtastic.core.repository.usecase.SendMessageUseCase
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class MessageViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
open class MessageViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val nodeRepository: NodeRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,22 +14,17 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class QuickChatViewModel @Inject constructor(private val quickChatActionRepository: QuickChatActionRepository) :
|
||||
ViewModel() {
|
||||
open class QuickChatViewModel(private val quickChatActionRepository: QuickChatActionRepository) : ViewModel() {
|
||||
val quickChatActions
|
||||
get() = quickChatActionRepository.getAllActions().stateInWhileSubscribed(initialValue = emptyList())
|
||||
|
||||
|
|
@ -21,7 +21,6 @@ import androidx.lifecycle.viewModelScope
|
|||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
|
@ -39,13 +38,9 @@ import org.meshtastic.core.repository.RadioConfigRepository
|
|||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.map as collectionsMap
|
||||
|
||||
@HiltViewModel
|
||||
class ContactsViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
open class ContactsViewModel(
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val packetRepository: PacketRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.messaging.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import org.meshtastic.feature.messaging.domain.worker.WorkManagerMessageQueue
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class MessagingModule {
|
||||
|
||||
@Binds abstract fun bindMessageQueue(impl: WorkManagerMessageQueue): MessageQueue
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.messaging.domain.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
|
||||
@HiltWorker
|
||||
class SendMessageWorker
|
||||
@AssistedInject
|
||||
constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val radioController: RadioController,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught", "SwallowedException", "ReturnCount")
|
||||
override suspend fun doWork(): Result {
|
||||
val packetId = inputData.getInt(KEY_PACKET_ID, 0)
|
||||
if (packetId == 0) return Result.failure()
|
||||
|
||||
// Verify we are connected before attempting to send to avoid unnecessary Exception bubbling
|
||||
if (radioController.connectionState.value != ConnectionState.Connected) {
|
||||
return Result.retry()
|
||||
}
|
||||
|
||||
val packetData =
|
||||
packetRepository.getPacketByPacketId(packetId)
|
||||
?: return Result.failure() // Packet no longer exists in DB? Do not retry.
|
||||
|
||||
return try {
|
||||
radioController.sendMessage(packetData)
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.ENROUTE)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.QUEUED)
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_PACKET_ID = "packet_id"
|
||||
const val WORK_NAME_PREFIX = "send_message_"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.messaging.domain.worker
|
||||
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Android implementation of [MessageQueue] that uses [WorkManager] for reliable background transmission. */
|
||||
@Singleton
|
||||
class WorkManagerMessageQueue @Inject constructor(private val workManager: WorkManager) : MessageQueue {
|
||||
|
||||
override suspend fun enqueue(packetId: Int) {
|
||||
val workRequest =
|
||||
OneTimeWorkRequestBuilder<SendMessageWorker>()
|
||||
.setInputData(workDataOf(SendMessageWorker.KEY_PACKET_ID to packetId))
|
||||
.build()
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
"${SendMessageWorker.WORK_NAME_PREFIX}$packetId",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.messaging.domain.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.testing.TestListenableWorkerBuilder
|
||||
import androidx.work.workDataOf
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class SendMessageWorkerTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var packetRepository: PacketRepository
|
||||
private lateinit var radioController: RadioController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
packetRepository = mockk(relaxed = true)
|
||||
radioController = mockk(relaxed = true)
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Connected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `doWork returns success when packet is sent successfully`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Connected)
|
||||
coEvery { radioController.sendMessage(any()) } just Runs
|
||||
coEvery { packetRepository.updateMessageStatus(any(), any()) } just Runs
|
||||
|
||||
val worker =
|
||||
TestListenableWorkerBuilder<SendMessageWorker>(context)
|
||||
.setInputData(workDataOf(SendMessageWorker.KEY_PACKET_ID to packetId))
|
||||
.setWorkerFactory(
|
||||
object : androidx.work.WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters,
|
||||
): ListenableWorker? =
|
||||
SendMessageWorker(appContext, workerParameters, packetRepository, radioController)
|
||||
},
|
||||
)
|
||||
.build()
|
||||
|
||||
// Act
|
||||
val result = worker.doWork()
|
||||
|
||||
// Assert
|
||||
assertEquals(ListenableWorker.Result.success(), result)
|
||||
coVerify { radioController.sendMessage(dataPacket) }
|
||||
coVerify { packetRepository.updateMessageStatus(dataPacket, MessageStatus.ENROUTE) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `doWork returns retry when radio is disconnected`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
|
||||
val worker =
|
||||
TestListenableWorkerBuilder<SendMessageWorker>(context)
|
||||
.setInputData(workDataOf(SendMessageWorker.KEY_PACKET_ID to packetId))
|
||||
.setWorkerFactory(
|
||||
object : androidx.work.WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters,
|
||||
): ListenableWorker? =
|
||||
SendMessageWorker(appContext, workerParameters, packetRepository, radioController)
|
||||
},
|
||||
)
|
||||
.build()
|
||||
|
||||
// Act
|
||||
val result = worker.doWork()
|
||||
|
||||
// Assert
|
||||
assertEquals(ListenableWorker.Result.retry(), result)
|
||||
coVerify(exactly = 0) { radioController.sendMessage(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `doWork returns failure when packet is missing`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 999
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns null
|
||||
|
||||
val worker =
|
||||
TestListenableWorkerBuilder<SendMessageWorker>(context)
|
||||
.setInputData(workDataOf(SendMessageWorker.KEY_PACKET_ID to packetId))
|
||||
.setWorkerFactory(
|
||||
object : androidx.work.WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters,
|
||||
): ListenableWorker? =
|
||||
SendMessageWorker(appContext, workerParameters, packetRepository, radioController)
|
||||
},
|
||||
)
|
||||
.build()
|
||||
|
||||
// Act
|
||||
val result = worker.doWork()
|
||||
|
||||
// Assert
|
||||
assertEquals(ListenableWorker.Result.failure(), result)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue