Dagger missing binding with same type of DataStore - dagger-2

In my project, I have two DataStore ( better API of SharedPref ). I have AuthDataStore and UserDataStore with separate packages. I want to Inject those two DataStores in separate repositories. I used Dagger Hilt
implementation 'com.google.dagger:hilt-android:2.40.5'
kapt 'com.google.dagger:hilt-android-compiler:2.40.5'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
This is my DataStoreModule
#Module
#InstallIn(SingletonComponent::class)
object DsModule {
#Provides
#Singleton
fun provideUserDataStoreSource(
ds: DataStore<Preferences>
): UserDataStoreSource {
return UserDataStoreSourceImpl(ds = ds)
}
#Provides
#Named(value = Constants.USER_DS_NAME)
#Singleton
fun provideUserDataStore(
#ApplicationContext context: Context
): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
produceFile = {
context.preferencesDataStoreFile(Constants.USER_DS_NAME)
}
)
}
#Provides
#Singleton
fun provideAuthDataStoreSource(
ds: DataStore<Preferences>
): AuthDataStore {
return AuthDataStoreSourceImpl(ds = ds)
}
#Provides
#Named(value = Constants.AUTH_DS_NAME)
#Singleton
fun provideAuthDataStore(
#ApplicationContext context: Context
): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
produceFile = {
context.preferencesDataStoreFile(Constants.AUTH_DS_NAME)
}
)
}
}
This is my repositories.
open class AuthDataStoreSourceImpl #Inject constructor(
#Named(value = Constants.AUTH_DS_NAME) private val ds: DataStore<Preferences>
) : AuthDataStore {
companion object {
val AUTH_STATE = booleanPreferencesKey("com.galaxytechno.auth_state")
}
override suspend fun putAuthFlag(isLoggedIn: Boolean) {
ds.edit {
it[AUTH_STATE] = isLoggedIn
}
}
override suspend fun pullAuthFlag(): Flow<Boolean> {
return ds.data
.catch { exception ->
if (exception is IOException) emit(emptyPreferences()) else throw exception
}.map {
it[AUTH_STATE] ?: false
}
}
}
open class UserDataStoreSourceImpl #Inject constructor(
#Named(value = Constants.USER_DS_NAME) private val ds: DataStore<Preferences>
) : UserDataStoreSource {
companion object {
val ACCESS_TOKEN = stringPreferencesKey("com.galaxytechno.user.access_token")
val REFRESH_TOKEN = stringPreferencesKey("com.galaxytechno.user.refresh_token")
}
override suspend fun putAccessToken(token: String) {
ds.edit {
it[ACCESS_TOKEN] = token
}
}
override suspend fun pullAccessToken(): Flow<String> {
return ds.data
.catch { exception ->
if (exception is IOException) emit(emptyPreferences()) else throw exception
}
.map {
it[ACCESS_TOKEN] ?: "empty_access_token"
}
} }
These are my main repos:
class AuthRepositoryImpl #Inject constructor(
private val api: AuthApiService,
#Named(value = Constants.AUTH_DS_NAME) private val ds: AuthDataStore,
#Qualifier.Io private val io: CoroutineDispatcher
) : AuthRepository {
override suspend fun login(
mobileNumber: String,
password: String
): Flow<RemoteResource<LoginDTO>> {
return flow {
emit(
safeApiCall {
api.login(
mobileNumber = mobileNumber,
password = password
)
}
)
}.flowOn(io)
}
override suspend fun putAuthFlag(isLoggedIn: Boolean) {
withContext(io){
ds.putAuthFlag(isLoggedIn = isLoggedIn)
}
}
override suspend fun pullAuthFlag(): Flow<Boolean> {
return ds.pullAuthFlag()
}
}
class UserRepositoryImpl #Inject constructor(
private val api: UserApiService,
private val db: UserDatabase,
#Named(value = Constants.USER_DS_NAME) private val ds : UserDataStoreSource,
#Qualifier.Io private val io: CoroutineDispatcher
) : UserRepository {
}
This is my Repo module;
#Module
#InstallIn(SingletonComponent::class)
object RepositoryModule {
#Provides
#Singleton
fun provideUserRepository(
api: UserApiService,
db: UserDatabase,
#Named(value = Constants.USER_DS_NAME) ds : UserDataStoreSource,
#Qualifier.Io io: CoroutineDispatcher
): UserRepository {
return UserRepositoryImpl(
api = api,
db = db,
ds = ds,
io = io
)
}
#Provides
#Singleton
fun provideAuthRepository(
api: AuthApiService,
#Named(value = Constants.AUTH_DS_NAME) ds: AuthDataStore,
#Qualifier.Io io: CoroutineDispatcher
): AuthRepository {
return AuthRepositoryImpl(
api = api,
ds = ds,
io = io
)
}
}
I have no idea with this. I read hilt documentation a lot. But I have lack of knowledge with Dagger. Please help me.
My error :
D:\galaxy_techno\Chat\app\build\generated\source\kapt\debug\com\galaxytechno\chat\app\ChatApp_HiltComponents.java:140: error: [Dagger/MissingBinding] #javax.inject.Named("auth.chat.ds") com.galaxytechno.chat.auth.data.ds.AuthDataStore cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements ChatApp_GeneratedInjector,
^
#javax.inject.Named("auth.chat.ds") com.galaxytechno.chat.auth.data.ds.AuthDataStore is injected at
com.galaxytechno.chat.app.di.RepositoryModule.provideAuthRepository(�, ds, �)
com.galaxytechno.chat.auth.domain.repository.AuthRepository is injected at
com.galaxytechno.chat.auth.domain.usecase.GetAuthStateUseCase(repo)
com.galaxytechno.chat.auth.domain.usecase.GetAuthStateUseCase is injected at
com.galaxytechno.chat.core.presentation.MainViewModel(getAuthStateUseCase)
com.galaxytechno.chat.core.presentation.MainViewModel is injected at
com.galaxytechno.chat.core.presentation.MainViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.galaxytechno.chat.app.ChatApp_HiltComponents.SingletonC ? com.galaxytechno.chat.app.ChatApp_HiltComponents.ActivityRetainedC ? com.galaxytechno.chat.app.ChatApp_HiltComponents.ViewModelC]

According to the [Dagger/MissingBinding] line, Dagger is looking for a #Named("auth.chat.ds") AuthDataStore. This means that you need an exact binding match, typically provided using #Binds or #Provides, including the exact class name, exact generics, and exact #Qualifier annotation (like #Named) with the exact same annotation values.
However, your DataStoreModule lists:
#Provides
#Singleton // No #Named annotation
fun provideAuthDataStoreSource(
ds: DataStore<Preferences>
): AuthDataStore { // AuthDataStore
return /* ... */
}
#Provides
#Named(value = Constants.AUTH_DS_NAME) // #Named("auth.chat.ds")
#Singleton
fun provideAuthDataStore(
#ApplicationContext context: Context
): DataStore<Preferences> { // DataStore<Preferences>
return /* ... */
}
So your parameter in provideAuthModule, #Named(value = Constants.AUTH_DS_NAME) ds: AuthDataStore, doesn't match. It's neither #Named(AUTH_DS_NAME) DataStore<Preferences> nor the unnamed/unqualified AuthDataStore.
Remove your #Named and you should be fine:
#Provides
#Singleton
fun provideAuthRepository(
api: AuthApiService,
/* #Named(value = Constants.AUTH_DS_NAME) */ ds: AuthDataStore,
/* ^-------------remove-----------------^ */
#Qualifier.Io io: CoroutineDispatcher
): AuthRepository

Related

How do I mock a property on a Scala companion object using Mockito and ScalaTest?

I have a simple companion Object that is creating a connection pool that should be globally static.
object CoreDataSource {
val pool : DataSource = getDataSource
def getDataSource: DataSource = {
...
}
}
I then use that in a variety of classes like...
class Query {
var source: DataSource = CoreDataSource.pool
def getSalutations: String = {
val conn : Connection = source.getConnection
...
}
}
Now I would like to test the getSalutations without the getDataSource actually getting called. Is there a way to mock out this call so that when the initial getDataSource call happens it hits a mock instead of the actual function?
Here is the skeleton of my test suite...
#RunWith(classOf[JUnitRunner])
class MySuite extends AnyFunSuite with MockitoSugar {
test("Test mocking the Datasource") {
// Mock object here
}
}
I Also tried...
var pool : Option[DataSource] = None
def getConnection: Connection = {
if(pool.isEmpty)
pool = getDataSource
pool.get.getConnection
}
def getDataSource: Option[DataSource] = {
...
}
}
#RunWith(classOf[JUnitRunner])
class MySuite extends AnyFunSuite with MockitoSugar with Matchers {
test("Test mocking the Datasource") {
withObjectMocked[CoreDataSource.type]{
val source = mock[DataSource]
when(CoreDataSource.getDataSource).thenReturn(Some(source))
...
assert(result == "")
}
}
}
But this throws
Mockito cannot mock/spy because :
- final class
I am not going to accept my workaround because it is ugly but this ended up working...
class CoreDataSource {
def getConnection = {
CoreDataSource.getConnection
}
}
object CoreDataSource {
var pool : Option[DataSource] = None
def getConnection: Connection = {
if(pool.isEmpty)
pool = getDataSource
pool.get.getConnection
}
def getDataSource: Option[DataSource] = {
...
}
}
class Query(ds: CoreDataSource = new CoreDataSource) {
def getSalutations: String = {
...
}
}
Then the following will work...
test("Single record returned") {
val cds = mock[CoreDataSource]
val source = mock[Connection]
doNothing.when(source).close()
when(cds.getConnection).thenReturn(source)
val q = new Query(cds)
val result = q.getSalutations
assert(result == "This is the test value")
}

How do you test the read-side processor in Lagom?

I have a read-side that is supposed to write entries to Cassandra, I would like to write a test that ensure that sends an event to the read-side and then check in Cassandra that the row has indeed been written. How am I supposed to access a Cassandra session within the test?
I do it following way:
class MyProcessorSpec extends AsyncWordSpec with BeforeAndAfterAll with Matchers {
private val server = ServiceTest.startServer(ServiceTest.defaultSetup.withCassandra(true)) { ctx =>
new MyApplication(ctx) {
override def serviceLocator = NoServiceLocator
override lazy val readSide: ReadSideTestDriver = new ReadSideTestDriver
}
}
override def afterAll(): Unit = server.stop()
private val testDriver = server.application.readSide
private val repository = server.application.repo
private val offset = new AtomicInteger()
"The event processor" should {
"create an entity" in {
for {
_ <- feed(createdEvent.id, createdEvent)
entity <- repository.getEntityIdByKey(createdEvent.keys.head)
entities <- repository.getAllEntities
} yield {
entity should be(Some(createdEvent.id))
entities.length should be(1)
}
}
}
private def feed(id: MyId, event: MyEvent): Future[Done] = {
testDriver.feed(id.underlying, event, Sequence(offset.getAndIncrement))
}
}

'initialize' called before #FXML variables bound

In my Scala application, I load the FXML file in the constructor of the controller and set the controller with fxmlLoader.setController(this).
UPDATE (1): A more comprehensive example:
abstract class Controller[A <: Parent] {
val root: A = loadRoot()
private val stage: Stage = new Stage()
def openWindow(): Unit = {
stage.setScene(new Scene(root))
stage.show()
stage.toFront()
}
private def loadRoot(): A = {
val loader = new FXMLLoader(getDefaultLocation())
loader.setController(this)
loader.load()
}
def getDefaultLocation(): URL = ???
}
--
class SampleController private() extends Controller[VBox] {
#FXML private var text: TextField = _
#FXML def initialize(): Unit = {
text.textProperty().set("That is some text.")
}
}
object SampleController {
def apply(): SampleController = new SampleController()
}
UPDATE (2): SampleController() is called whithin an Akka actor:
val controller = SampleController()
Platform.runLater(() => controller.openWindow())
I now experience that sometimes the initialize method is called before the c variables are bound. Can anyone think of any circumstances when that can happen?

scala: how to use a class like a variable

Is it possible to refer to different classes on each pass of an iteration?
I have a substantial number of Hadoop Hive tables, and will be processing them with Spark. Each of the tables has an auto-generated class, and I would like to loop through the tables, instead of the tedious, non-code reuse copy/paste/handCodeIndividualTableClassNames technique resorted to first.
import myJavaProject.myTable0Class
import myJavaProject.myTable1Class
object rawMaxValueSniffer extends Logging {
/* tedious sequential: it works, and sometimes a programmer's gotta do... */
def tedious(args: Array[String]): Unit = {
val tablePaths = List("path0_string_here","path1_string")
var maxIds = ArrayBuffer[Long]()
FileInputFormat.setInputPaths(conf, tablePaths(0))
AvroReadSupport.setAvroReadSchema(conf.getConfiguration, myTable0Class.getClassSchema)
ParquetInputFormat.setReadSupportClass(conf, classOf[AvroReadSupport[myTable0Class]])
val records = sc.newAPIHadoopRDD(conf.getConfiguration,
classOf[ParquetInputFormat[myTable0Class]],
classOf[Void],
classOf[myTable0Class]).map(x => x._2)
maxIds += records.map(_.getId).collect().max
FileInputFormat.setInputPaths(conf, tablePaths(1))
AvroReadSupport.setAvroReadSchema(conf.getConfiguration, myTable1Class.getClassSchema)
ParquetInputFormat.setReadSupportClass(conf, classOf[AvroReadSupport[myTable1Class]])
val records = sc.newAPIHadoopRDD(conf.getConfiguration,
classOf[ParquetInputFormat[myTable1Class]],
classOf[Void],
classOf[myTable1Class]).map(x => x._2)
maxIds += records.map(_.getId).collect().max
}
/* class as variable, used in a loop. I have seen the mountain... */
def hopedFor(args: Array[String]): Unit = {
val tablePaths = List("path0_string_here","path1_string")
var maxIds = ArrayBuffer[Long]()
val tableClasses = List(classOf[myTable0Class],classOf[myTable1Class]) /* error free, but does not get me where I'm trying to go */
var counter=0
tableClasses.foreach { tc =>
FileInputFormat.setInputPaths(conf, tablePaths(counter))
AvroReadSupport.setAvroReadSchema(conf.getConfiguration, tc.getClassSchema)
ParquetInputFormat.setReadSupportClass(conf, classOf[AvroReadSupport[tc]])
val records = sc.newAPIHadoopRDD(conf.getConfiguration,
classOf[ParquetInputFormat[tc]],
classOf[Void],
classOf[tc]).map(x => x._2)
maxIds += records.map(_.getId).collect().max /* all the myTableXXX classes have getId() */
counter += 1
}
}
}
/* the classes being referenced... */
#org.apache.avro.specific.AvroGenerated
public class myTable0Class extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"rsivr_surveyquestiontypes\",\"namespace\":\"myJavaProject\",\"fields\":[{\"name\":\"id\",\"type\":\"in t\"},{\"name\":\"description\",\"type\":\"st,ing\"},{\"name\":\"scale_range\",\"type\":\"int\"}]}");
public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
#Deprecated public int id;
yada.yada.yada0
}
#org.apache.avro.specific.AvroGenerated
public class myTable1Class extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"rsivr_surveyresultdetails\",\"namespace\":\"myJavaProject\",\"fields\":[{\"name\":\"id\",\"type\":\"in t\"},{\"name\":\"survey_dts\",\"type\":\"string\"},{\"name\":\"survey_id\",\"type\":\"int\"},{\"name\":\"question\",\"type\":\"int\"},{\"name\":\"caller_id\",\"type\":\"string\"},{\"name\":\"rec_msg\",\"type\":\"string\"},{\"name\ ":\"note\",\"type\":\"string\"},{\"name\":\"lang\",\"type\":\"string\"},{\"name\":\"result\",\"type\":\"string\"}]}");
public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
#Deprecated public int id;
yada.yada.yada1
}
Something like this, perhaps:
def doStuff[T <: SpecificRecordBase : ClassTag](index: Int, schema: => Schema, clazz: Class[T]) = {
FileInputFormat.setInputPaths(conf, tablePaths(index))
AvroReadSupport.setAvroReadSchema(conf.getConfiguration, schema)
ParquetInputFormat.setReadSupportClass(conf, classOf[AvroReadSupport[T]])
val records = sc.newAPIHadoopRDD(conf.getConfiguration,
classOf[ParquetInputFormat[T]],
classOf[Void],
clazz).map(x => x._2)
maxIds += records.map(_.getId).collect().max
}
Seq(
(classOf[myTable0Class], myTable0Class.getClassSchema _),
(classOf[myTable1Class], myTable1Class.getClassSchema _)
).zipWithIndex
.foreach { case ((clazz, schema), index) => doStuff(index, schema, clazz) }
You could use reflection to invoke getClassSchema instead (clazz.getMethod("getClassSchema").invoke(null).asInstanceOf[Schema]), then you would not need to pass it in as a aprameter, just clazz would be enough, but that's kinda cheating ... I like this approach a bit better.

How do I extract the value of a route variable from the URL in a Scala-Play app?

I am writing a module for the Play Framework. In part of my module I have the following code
abstract class SecurityFiltering extends GlobalSettings{
override def onRequestReceived(request: RequestHeader) = {
play.Logger.debug("onRequestReceived: " + request)
super.onRequestReceived(request)
}
override def doFilter(next: RequestHeader => Handler): (RequestHeader => Handler) = {
request => {
play.Logger.debug("doFilter: " + request)
super.doFilter(next)(request)
}
}
override def onRouteRequest(request: RequestHeader): Option[Handler] = {
play.Logger.debug("onRouteRequest: " + request)
super.onRouteRequest(request)
}
}
In the doFilter method I am able to determine the following useful information
ROUTE_PATTERN = /x/$name<[^/]+>/$age<[^/]+>
ROUTE_CONTROLLER = controllers.Application
ROUTE_ACTION_METHOD = tester
ROUTE_VERB = GET
path = /x/hello
What I need in addition to this is the values for the named parts of the URL before the QueryString. So given the following route in my test application I need to retrieve Name=Pete and Age=41
localhost:9000/x/Pete/41
There is surely some code in the Play Framework which already does this but I am unable to find it. Can someone suggest how I achieve this goal, or point me at which Play class extracts these values?
package models.com.encentral.tattara.web.util;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RouteExtractor {
//something like "/foo/$id<[^/]+>/edit/$james<[^/]+>"
private String routePattern;
private String path;
//something like /$id<[^/]+>
private static final String INDEX_PATTERN = "\\$(.+?)\\<\\[\\^\\/\\]\\+\\>";
public RouteExtractor(String routePattern, String path) {
this.routePattern = routePattern;
this.path = path;
}
private Map<Integer, String> extractPositions() {
Pattern pattern = Pattern.compile(INDEX_PATTERN);
Matcher matcher = pattern.matcher(this.routePattern);
Map<Integer, String> results = new HashMap<>();
int index = 0;
while (matcher.find()) {
results.put(index++, matcher.group(1));
}
return results;
}
private String replaceRoutePatternWithGroup() {
Pattern pattern = Pattern.compile(INDEX_PATTERN);
Matcher matcher = pattern.matcher(this.routePattern);
return matcher.replaceAll("([^/]+)");
}
public Map<String, String> extract() {
Pattern pattern = Pattern.compile(this.replaceRoutePatternWithGroup());
Matcher matcher = pattern.matcher(this.path);
final Map<String, String> results = new HashMap<>();
if (matcher.find()) {
this.extractPositions().entrySet().stream().forEach(s -> {
results.put(s.getValue(), matcher.group(s.getKey() + 1));
});
}
return results;
}
}
As per this GitHub issue response via JRoper
onRequestReceived is the thing that does the routing and tags the request, so of course it's not going to have any of the routing information when it's first invoked, only after it's invoked.
val p = """\$([^<]+)<([^>]+)>""".r
override def onRequestReceived(request: RequestHeader) = {
val (taggedRequest, handler) = super.onRequestReceived(request)
val pattern = taggedRequest.tags("ROUTE_PATTERN")
val paramNames = p.findAllMatchIn(pattern).map(m => m.group(1)).toList
val pathRegex = ("^" + p.replaceAllIn(pattern, m => "(" + m.group(2) + ")") + "$").r
val paramValues = pathRegex.findFirstMatchIn(request.path).get.subgroups
val params: Map[String, String] = paramNames.zip(paramValues).toMap
// ^ your params map, will be Map("name" -> "Pete", "age" -> "41")
(taggedRequest, handler)
}
That said, there are usually better, more typesafe ways to achieve whatever you're trying to achieve. If you depend on there being specific parameters in the URL, then a filter is not the right thing, because filters apply to all requests, whether they have those parameters or not. Rather, you should probably be using action composition or a custom action builder, like so:
case class MyAction(name: String, age: Int) extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// Do your filtering here, you have access to both the name and age above
block(request)
}
}
def foo(name: String, age: Int) = MyAction(name, age) { request =>
Ok("Hello world")
}
def bar(name: String, age: Int) = MyAction(name, age).async { request =>
Future.successful(Ok("Hello world"))
}