Merge pull request #360 from mcumings/hilt-db-init

Introduce Hilt dependency injection
pull/370/head
Andre Kirchhoff 2022-02-08 19:35:58 -03:00 zatwierdzone przez GitHub
commit 413a6b9d52
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
18 zmienionych plików z 131 dodań i 66 usunięć

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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
}
}
}
}

Wyświetl plik

@ -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<List<Packet>> = packetDao.getAllPacket(MAX_ITEMS)
val allPacketsInReceiveOrder : Flow<List<Packet>> = packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS)
class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Lazy<PacketDao>) {
private val packetDao by lazy {
packetDaoLazy.get()
}
suspend fun insert(packet: Packet) {
suspend fun getAllPackets(): Flow<List<Packet>> = withContext(Dispatchers.IO) {
packetDao.getAllPacket(MAX_ITEMS)
}
suspend fun getAllPacketsInReceiveOrder(): Flow<List<Packet>> = 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
}
}

Wyświetl plik

@ -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<List<Packet>>
fun getAllPacket(maxItem: Int): Flow<List<Packet>>
@Query("Select * from packet order by received_date asc limit 0,:maxItem")
fun getAllPacketsInReceiveOrder(maxItem: Int): Flow<List<Packet>>

Wyświetl plik

@ -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<List<Packet>>
private val _allPacketState = MutableStateFlow<List<Packet>>(emptyList())
val allPackets: StateFlow<List<Packet>> = _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<Boolean>(false) {
}
val provideLocation = object : MutableLiveData<Boolean>(getPreferences(context).getBoolean(MyPreferences.provideLocationKey, false)) {
val provideLocation = object : MutableLiveData<Boolean>(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) {

Wyświetl plik

@ -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<PacketRepository>
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)
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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) }
})
}
}
}

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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"
}
}