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