From 654a32c01c7a6b90a0805ff01acac367e8fa2225 Mon Sep 17 00:00:00 2001 From: Mike Cumings Date: Tue, 8 Feb 2022 13:50:21 -0800 Subject: [PATCH] Introduce Hilt dependency injection Uses Hilt to get the database initialization off of the main thread. The initial introduction always has a disproportionate fan-out of boilerplate. In this case, all entry points which were using UIViewModel needed to be annotated in order to let the code gen know that they needed to support it. The PacketRepository is injected into things via the main thread (e.g., the MeshService) but due to the lazy declaration, the database isn't hydrated until the DAO is access while on an IO thread. --- app/build.gradle | 7 ++++ .../com/geeksville/mesh/ApplicationModule.kt | 18 ++++++++ .../java/com/geeksville/mesh/MainActivity.kt | 2 + .../geeksville/mesh/MeshUtilApplication.kt | 7 +++- .../mesh/database/DatabaseModule.kt | 29 +++++++++++++ .../mesh/database/MeshtasticDatabase.kt | 25 ----------- .../mesh/database/PacketRepository.kt | 24 +++++++---- .../geeksville/mesh/database/dao/PacketDao.kt | 3 +- .../java/com/geeksville/mesh/model/UIState.kt | 42 +++++++++++-------- .../geeksville/mesh/service/MeshService.kt | 15 ++++--- .../mesh/ui/AdvancedSettingsFragment.kt | 3 +- .../com/geeksville/mesh/ui/ChannelFragment.kt | 2 + .../com/geeksville/mesh/ui/DebugFragment.kt | 8 ++-- .../com/geeksville/mesh/ui/MapFragment.kt | 2 + .../geeksville/mesh/ui/MessagesFragment.kt | 2 + .../geeksville/mesh/ui/SettingsFragment.kt | 2 + .../com/geeksville/mesh/ui/UsersFragment.kt | 3 +- build.gradle | 3 ++ 18 files changed, 131 insertions(+), 66 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/ApplicationModule.kt create mode 100644 app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt diff --git a/app/build.gradle b/app/build.gradle index 05c86b0a..5afadfa3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' +apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.github.triplet.play' apply plugin: 'de.mobilej.unmock' @@ -135,7 +136,9 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' implementation "androidx.room:room-runtime:$room_version" + implementation "com.google.dagger:hilt-android:$hilt_version" kapt "androidx.room:room-compiler:$room_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" // optional - Kotlin Extensions and Coroutines support for Room @@ -200,3 +203,7 @@ dependencies { implementation project(':geeksville-androidlib') } + +kapt { + correctErrorTypes true +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt b/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt new file mode 100644 index 00000000..834bfea7 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt @@ -0,0 +1,18 @@ +package com.geeksville.mesh + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +object ApplicationModule { + @Provides + fun provideSharedPreferences(application: Application): SharedPreferences { + return application.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 3acc7c01..e62d809e 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -62,6 +62,7 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator import com.vorlonsoft.android.rate.AppRate import com.vorlonsoft.android.rate.StoreType +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -123,6 +124,7 @@ eventually: val utf8 = Charset.forName("UTF-8") +@AndroidEntryPoint class MainActivity : AppCompatActivity(), Logging, ActivityCompat.OnRequestPermissionsResultCallback { diff --git a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt index af8a643e..76d2d9ed 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt @@ -7,8 +7,13 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.util.Exceptions import com.google.firebase.crashlytics.FirebaseCrashlytics +import dagger.hilt.android.HiltAndroidApp -class MeshUtilApplication : GeeksvilleApplication() { +// NOTE: This is a workaround since the Hilt Gradle plugin doesn't support constructors with default parameters +open class GeeksvilleApplicationWrapper : GeeksvilleApplication() + +@HiltAndroidApp +class MeshUtilApplication : GeeksvilleApplicationWrapper() { override fun onCreate() { super.onCreate() diff --git a/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt b/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt new file mode 100644 index 00000000..ec8d4cb2 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt @@ -0,0 +1,29 @@ +package com.geeksville.mesh.database + +import android.app.Application +import androidx.room.Room +import com.geeksville.mesh.database.dao.PacketDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +class DatabaseModule { + @Provides + fun provideDatabase(application: Application): MeshtasticDatabase { + return Room.databaseBuilder( + application.applicationContext, + MeshtasticDatabase::class.java, + "meshtastic_database" + ) + .fallbackToDestructiveMigration() + .build() + } + + @Provides + fun providePacketDao(database: MeshtasticDatabase): PacketDao { + return database.packetDao() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt index a384ca0e..b8857a51 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -1,8 +1,6 @@ package com.geeksville.mesh.database -import android.content.Context import androidx.room.Database -import androidx.room.Room import androidx.room.RoomDatabase import com.geeksville.mesh.database.dao.PacketDao import com.geeksville.mesh.database.entity.Packet @@ -10,27 +8,4 @@ import com.geeksville.mesh.database.entity.Packet @Database(entities = [Packet::class], version = 1, exportSchema = false) abstract class MeshtasticDatabase : RoomDatabase() { abstract fun packetDao(): PacketDao - - companion object { - @Volatile - private var INSTANCE: MeshtasticDatabase? = null - - fun getDatabase( - context: Context - ): MeshtasticDatabase { - return INSTANCE ?: synchronized(this) { - val instance = Room.databaseBuilder( - context.applicationContext, - MeshtasticDatabase::class.java, - "meshtastic_database" - ) - .fallbackToDestructiveMigration() - .build() - INSTANCE = instance - - instance - } - } - } - } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt index 060cc446..06a7ff84 100644 --- a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt @@ -1,24 +1,34 @@ package com.geeksville.mesh.database -import androidx.lifecycle.LiveData import com.geeksville.mesh.database.dao.PacketDao import com.geeksville.mesh.database.entity.Packet +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import javax.inject.Inject -class PacketRepository(private val packetDao : PacketDao) { - val allPackets : LiveData> = packetDao.getAllPacket(MAX_ITEMS) - val allPacketsInReceiveOrder : Flow> = packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS) +class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Lazy) { + private val packetDao by lazy { + packetDaoLazy.get() + } - suspend fun insert(packet: Packet) { + suspend fun getAllPackets(): Flow> = withContext(Dispatchers.IO) { + packetDao.getAllPacket(MAX_ITEMS) + } + + suspend fun getAllPacketsInReceiveOrder(): Flow> = withContext(Dispatchers.IO) { + packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS) + } + + suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) { packetDao.insert(packet) } - suspend fun deleteAll() { + suspend fun deleteAll() = withContext(Dispatchers.IO) { packetDao.deleteAll() } companion object { private const val MAX_ITEMS = 500 } - } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt index 55d33d7b..af02e713 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt @@ -1,6 +1,5 @@ package com.geeksville.mesh.database.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.Query @@ -11,7 +10,7 @@ import kotlinx.coroutines.flow.Flow interface PacketDao { @Query("Select * from packet order by received_date desc limit 0,:maxItem") - fun getAllPacket(maxItem: Int): LiveData> + fun getAllPacket(maxItem: Int): Flow> @Query("Select * from packet order by received_date asc limit 0,:maxItem") fun getAllPacketsInReceiveOrder(maxItem: Int): Flow> diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index ec77dd70..c6ede21b 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -7,18 +7,20 @@ import android.net.Uri import android.os.RemoteException import android.view.Menu import androidx.core.content.edit -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.geeksville.android.Logging import com.geeksville.mesh.* -import com.geeksville.mesh.database.MeshtasticDatabase import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.ui.positionToMeter +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -26,6 +28,7 @@ import java.io.BufferedWriter import java.io.FileWriter import java.text.SimpleDateFormat import java.util.* +import javax.inject.Inject import kotlin.math.roundToInt /// Given a human name, strip out the first letter of the first three words and return that as the initials for @@ -51,20 +54,25 @@ fun getInitials(nameIn: String): String { return initials.take(nchars) } -class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging { +@HiltViewModel +class UIViewModel @Inject constructor( + private val app: Application, + private val repository: PacketRepository, + private val preferences: SharedPreferences +) : ViewModel(), Logging { - private val repository: PacketRepository - - val allPackets: LiveData> + private val _allPacketState = MutableStateFlow>(emptyList()) + val allPackets: StateFlow> = _allPacketState init { - val packetsDao = MeshtasticDatabase.getDatabase(app).packetDao() - repository = PacketRepository(packetsDao) - allPackets = repository.allPackets + viewModelScope.launch { + repository.getAllPackets().collect { packets -> + _allPacketState.value = packets + } + } debug("ViewModel created") } - fun insertPacket(packet: Packet) = viewModelScope.launch(Dispatchers.IO) { repository.insert(packet) } @@ -78,8 +86,6 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE) } - private val context: Context get() = app.applicationContext - var actionBarMenu: Menu? = null var meshService: IMeshService? = null @@ -208,7 +214,7 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging channels.value = c // Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings) - getPreferences(context).edit(commit = true) { + preferences.edit(commit = true) { this.putString("channel-url", c.getChannelUrl().toString()) } } @@ -226,11 +232,11 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging val bluetoothEnabled = object : MutableLiveData(false) { } - val provideLocation = object : MutableLiveData(getPreferences(context).getBoolean(MyPreferences.provideLocationKey, false)) { + val provideLocation = object : MutableLiveData(preferences.getBoolean(MyPreferences.provideLocationKey, false)) { override fun setValue(value: Boolean) { super.setValue(value) - getPreferences(context).edit(commit = true) { + preferences.edit(commit = true) { this.putBoolean(MyPreferences.provideLocationKey, value) } } @@ -246,7 +252,7 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging ownerName.value = s // note: we allow an empty userstring to be written to prefs - getPreferences(context).edit(commit = true) { + preferences.edit(commit = true) { putString("owner", s) } } @@ -286,7 +292,7 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging // our device in localNodePosition. var localNodePosition: MeshProtos.Position? = null val dateFormat = SimpleDateFormat("yyyy-MM-dd,HH:mm:ss", Locale.getDefault()) - repository.allPacketsInReceiveOrder.first().forEach { packet -> + repository.getAllPacketsInReceiveOrder().first().forEach { packet -> packet.proto?.let { proto -> packet.position?.let { position -> if (proto.from == myNodeNum) { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 1392734c..ec276708 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -22,7 +22,6 @@ import com.geeksville.mesh.* import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos.ToRadio import com.geeksville.mesh.android.hasBackgroundPermission -import com.geeksville.mesh.database.MeshtasticDatabase import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.model.DeviceVersion @@ -36,9 +35,12 @@ import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest import com.google.protobuf.ByteString import com.google.protobuf.InvalidProtocolBufferException +import dagger.Lazy +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* import kotlinx.serialization.json.Json import java.util.* +import javax.inject.Inject import kotlin.math.absoluteValue import kotlin.math.max @@ -49,7 +51,10 @@ import kotlin.math.max * Note: this service will go away once all clients are unbound from it. * Warning: do not override toString, it causes infinite recursion on some androids (because contextWrapper.getResources calls to string */ +@AndroidEntryPoint class MeshService : Service(), Logging { + @Inject + lateinit var packetRepository: Lazy companion object : Logging { @@ -119,9 +124,6 @@ class MeshService : Service(), Logging { private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) private var connectionState = ConnectionState.DISCONNECTED - /// A database of received packets - used only for debug log - private var packetRepo: PacketRepository? = null - private var fusedLocationClient: FusedLocationProviderClient? = null // If we've ever read a valid region code from our device it will be here @@ -326,9 +328,6 @@ class MeshService : Service(), Logging { info("Creating mesh service") - val packetsDao = MeshtasticDatabase.getDatabase(applicationContext).packetDao() - packetRepo = PacketRepository(packetsDao) - // Switch to the IO thread serviceScope.handledLaunch { loadSettings() // Load our last known node DB @@ -994,7 +993,7 @@ class MeshService : Service(), Logging { serviceScope.handledLaunch { // Do not log, because might contain PII // info("insert: ${packetToSave.message_type} = ${packetToSave.raw_message.toOneLineString()}") - packetRepo!!.insert(packetToSave) + packetRepository.get().insert(packetToSave) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt index ddd62ead..663f86f0 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt @@ -6,7 +6,6 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer import com.geeksville.android.Logging import com.geeksville.android.hideKeyboard import com.geeksville.mesh.R @@ -16,7 +15,9 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService import com.geeksville.util.exceptionToSnackbar import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { private val MAX_INT_DEVICE = 0xFFFFFFFF private var _binding: AdvancedSettingsBinding? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index daf62a33..254f1948 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -33,6 +33,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.protobuf.ByteString import com.google.zxing.integration.android.IntentIntegrator +import dagger.hilt.android.AndroidEntryPoint import java.security.SecureRandom @@ -51,6 +52,7 @@ fun ImageView.setOpaque() { imageAlpha = 255 } +@AndroidEntryPoint class ChannelFragment : ScreenFragment("Channel"), Logging { private var _binding: ChannelFragmentBinding? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt index 44f79eda..fc4abbbe 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt @@ -6,13 +6,15 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer +import androidx.lifecycle.asLiveData import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.geeksville.mesh.R import com.geeksville.mesh.databinding.DebugFragmentBinding import com.geeksville.mesh.model.UIViewModel +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class DebugFragment : Fragment() { private var _binding: DebugFragmentBinding? = null @@ -48,8 +50,8 @@ class DebugFragment : Fragment() { binding.closeButton.setOnClickListener { parentFragmentManager.popBackStack() } - model.allPackets.observe(viewLifecycleOwner, Observer { packets -> + model.allPackets.asLiveData().observe(viewLifecycleOwner) { packets -> packets?.let { adapter.setPackets(it) } - }) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index c3e588fe..2d799347 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -32,8 +32,10 @@ import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource import com.mapbox.maps.plugin.animation.MapAnimationOptions import com.mapbox.maps.plugin.animation.flyTo import com.mapbox.maps.plugin.gestures.gestures +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MapFragment : ScreenFragment("Map"), Logging { private val model: UIViewModel by activityViewModels() diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index bd0beefb..a7c90157 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -26,6 +26,7 @@ import com.geeksville.mesh.databinding.MessagesFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService import com.google.android.material.chip.Chip +import dagger.hilt.android.AndroidEntryPoint import java.text.DateFormat import java.util.* @@ -41,6 +42,7 @@ fun EditText.on(actionId: Int, func: () -> Unit) { } } +@AndroidEntryPoint class MessagesFragment : ScreenFragment("Messages"), Logging { private var _binding: MessagesFragmentBinding? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 4c63e412..93aa9c10 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -49,6 +49,7 @@ import com.google.android.gms.location.LocationSettingsRequest import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.hoho.android.usbserial.driver.UsbSerialDriver +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -439,6 +440,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { } @SuppressLint("NewApi") +@AndroidEntryPoint class SettingsFragment : ScreenFragment("Settings"), Logging { private var _binding: SettingsFragmentBinding? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 6a826c5d..2e93c72e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -9,7 +9,6 @@ import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.geeksville.android.Logging @@ -19,9 +18,11 @@ import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.util.formatAgo +import dagger.hilt.android.AndroidEntryPoint import java.net.URLEncoder import kotlin.math.roundToInt +@AndroidEntryPoint class UsersFragment : ScreenFragment("Users"), Logging { private var _binding: NodelistFragmentBinding? = null diff --git a/build.gradle b/build.gradle index c46b6bd7..6d31483b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { ext.kotlin_version = '1.6.10' ext.coroutines_version = "1.5.2" + ext.hilt_version = '2.40.5' repositories { google() @@ -30,6 +31,8 @@ buildscript { // for unit testing https://github.com/bjoernQ/unmock-plugin classpath 'com.github.bjoernq:unmockplugin:0.7.9' + + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" } }