How can I call the method which return Mono<> and use it to call web method itself?
#Component
class SampleWebFilter(private val sampleService: SampleService) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val accessToken =
exchange.request.headers["Authorization"]?.firstOrNull()
?: throw IllegalArgumentException("Access token must not be empty")
val res = sampleService.authorize(accessToken)
val id = res.block()?.userId
exchange.attributes["UserId"] = userId
return chain.filter(exchange)
}
}
#Component
interface SampleService {
#GET("/user")
fun authorize(accessToken): Mono<User>
}
the code above throw exception
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
I know we shouldn't block the thread on netty but how can I use id from the SampleService to call web method.
Thanks in advance.
#Component
class SampleWebFilter(private val sampleService: SampleService) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val accessToken =
exchange.request.headers["Authorization"]?.firstOrNull()
?: throw IllegalArgumentException("Access token must not be empty")
val res = sampleService.authorize(accessToken)
return res.doOnNext {
exchange.attributes["UserId"] = userId
}
.then(chain.filter(exchange))
}}
#Component
interface SampleService {
#GET("/user")
fun authorize(accessToken): Mono<User>
}
I solved the problem writing like above.
Related
My app using room as a database and retrofit as a network calling api.
i am observing database only as a single source of truth. every thing is working fine. But i am not finding solution of one scenario.
Like for the first time when user open app it do following operations
fetch data from db
fetch data from server
because currently database is empty so it sends empty result to observer which hide progress bar . i want to discard that event and send result to observer when server dump data to database. even server result is empty. so progress bar should always hide once their is confirmation no data exists.
in other words application should always rely on database but if it empty then it should wait until server response and then notify observer.
this is my code
observer
viewModel.characters.observe(viewLifecycleOwner, Observer {
Log.e("status is ", "${it.message} at ${System.currentTimeMillis()}")
when (it.status) {
Resource.Status.SUCCESS -> {
binding.progressBar.visibility = View.GONE
if (!it.data.isNullOrEmpty()) adapter.setItems(ArrayList(it.data))
}
Resource.Status.ERROR -> {
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
binding.progressBar.visibility = View.GONE
}
Resource.Status.LOADING ->
binding.progressBar.visibility = View.VISIBLE
}
})
ViewModel
#HiltViewModel
class CharactersViewModel #Inject constructor(
private val repository: CharacterRepository
) : ViewModel() {
val characters = repository.getCharacters()
}
Repository
class CharacterRepository #Inject constructor(
private val remoteDataSource: CharacterRemoteDataSource,
private val localDataSource: CharacterDao
) {
fun getCharacters() : LiveData<Resource<List<Character>>> {
return performGetOperation(
databaseQuery = { localDataSource.getAllCharacters() },
networkCall = { remoteDataSource.getCharacters() },
saveCallResult = { localDataSource.insertAll(it.results) }
)
}
}
Utility function for all api and database handling
fun <T, A> performGetOperation(databaseQuery: () -> LiveData<T>,
countQuery: () -> Int,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery().map { Resource.success(it,"database") }.distinctUntilChanged()
emitSource(source)
val responseStatus = networkCall()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
}
}
LocalDataSource
#Dao
interface CharacterDao {
#Query("SELECT * FROM characters")
fun getAllCharacters() : LiveData<List<Character>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(characters: List<Character>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(character: Character)
}
DataSource
class CharacterRemoteDataSource #Inject constructor(
private val characterService: CharacterService
): BaseDataSource() {
suspend fun getCharacters() = getResult { characterService.getAllCharacters() }}
}
Base Data Source
abstract class BaseDataSource {
protected suspend fun <T> getResult(call: suspend () -> Response<T>): Resource<T> {
try {
Log.e("status is", "started")
val response = call()
if (response.isSuccessful) {
val body = response.body()
if (body != null) return Resource.success(body,"server")
}
return error(" ${response.code()} ${response.message()}")
} catch (e: Exception) {
return error(e.message ?: e.toString())
}
}
private fun <T> error(message: String): Resource<T> {
Timber.d(message)
return Resource.error("Network call has failed for a following reason: $message")
}
}
Character Service
interface CharacterService {
#GET("character")
suspend fun getAllCharacters() : Response<CharacterList>
}
Resource
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T,message : String): Resource<T> {
return Resource(Status.SUCCESS, data, message)
}
fun <T> error(message: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, message)
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, "loading")
}
}
}
CharacterList
data class CharacterList(
val info: Info,
val results: List<Character>
)
What is the best way by that i ignore database if it is empty and wait for server response and then notify observer
I have a method for calling the "marshalSendAndReceive" method on sending a message over Soap.
class SoapConnector : WebServiceGatewaySupport() {
fun getClient(documentId: String): Person {
val request = getSearchRequest(documentId)
val response = webServiceTemplate.marshalSendAndReceive(request) as SearchResponse
return getPerson(response)
}
I also have a configuration file.
#Configuration
class SearchClientConfig {
#Bean
fun marshaller(): Jaxb2Marshaller? {
return Jaxb2Marshaller().apply {
contextPath = "wsdl" //here path to generated java classes from wsdl
}
}
#Bean
fun searchCilent(marshallerJaxb: Jaxb2Marshaller): SoapConnector {
return SoapConnector().apply {
defaultUri = "http://20.40.59.1:8080/cdi/soap/services/ServiceWS"
marshaller = marshallerJaxb
unmarshaller = marshallerJaxb
}
}
}
I want to Mock this method.
#RunWith(SpringRunner::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Sql(scripts = ["classpath:db/init.sql"])
#AutoConfigureEmbeddedDatabase(
beanName = "dataSource",
provider = AutoConfigureEmbeddedDatabase.DatabaseProvider.DOCKER
)
class SoapTest : WebServiceGatewaySupport(){
private lateinit var webServiceGatewaySupport: WebServiceGatewaySupport
private lateinit var connector: SoapConnector
#BeforeEach
fun setUp() {
webServiceGatewaySupport = Mockito.mock(WebServiceGatewaySupport::class.java)
}
#Test
fun getClientTest() {
Mockito.`when`(webServiceTemplate.marshalSendAndReceive(ArgumentMatchers.anyObject())).thenReturn(SearchResponse())
connector.getClient(RandomStringUtils.randomAlphabetic(7))
}
}
But there is a bug:
java.lang.IllegalArgumentException: 'uri' must not be empty
I don't understand what the problem is and I don't know how to make a working Mock of this method.
I'm trying to handle JWT-authentication in gRPC on my backend. I can extract the JWT in an interceptor but how do I access it in my service? I think it should be done with a CoroutineContextServerInterceptor but this doesn't work:
val jwtKey: Context.Key<String> = Context.key("jwtKey")
fun main() {
ServerBuilder.forPort(8980).intercept(UserInjector).addService(MyService).build().start().awaitTermination()
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return GrpcContextElement(Context.current().withValue(jwtKey, jwtString))
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${jwtKey.get()}")
return Test.MyResponse.getDefaultInstance()
}
}
Output:
coroutineContext: something
testingJWT: null
I think you'll need to propagate that in its own coroutine context element.
class JwtElement(val jwtString: String) : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<JwtElement>
override val key: CoroutineContext.Key<JwtElement>
get() = Key
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return JwtElement(jwtString)
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${coroutineContext[JwtElement]}")
return Test.MyResponse.getDefaultInstance()
}
}
I am starting a service in a different process from an activity.
The service is designed to run even when the app is closed.
After starting the service from the activity, I close the app. Now when I reopen the app the service may or may not be running. But I haven't find way to know if the service is running or not.
How can I achieve that?
FYI: I have checked all the related answers here on SO but none of them works when the service is running in a different process.
This is the closest answer I have got link. But this answer seems flawed, I would also like to hear your opinion on it too.
Here's what I am currently doing:
AndroidManifest.xml
<service
android:name=".services.MyService"
android:enabled="true"
android:exported="false"
android:process=":backgroundProcess" />
MainApplication.kt (purpose: to have only one instance of the SettingsRepository class)
class MainApplication : Application() {
val settingsRepository by lazy { SettingsRepository(this) }
}
SettingsRepository.kt (purpose: to save the running state of the service in Preference DataStore)
class SettingsRepository(context: Context) {
private val dataStore = context.createDataStore(name = "settings_prefs")
companion object {
val SERVICE_STATE_KEY = booleanPreferencesKey("SERVICE_STATE_KEY")
}
suspend fun saveServiceStateToDataStore(state: Boolean) {
dataStore.edit {
it[SERVICE_STATE_KEY] = state
}
}
val getServiceStateFromDataStore: Flow<Boolean> = dataStore.data.map {
val state = it[SERVICE_STATE_KEY] ?: false
state
}
}
Service.kt
private lateinit var settingsRepository: SettingsRepository
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
settingsRepository = (application.applicationContext as MainApplication).settingsRepository
saveStateToDataStore(true)
return START_REDELIVER_INTENT
}
private fun saveStateToDataStore(state: Boolean): Job {
return CoroutineScope(Dispatchers.IO).launch {
settingsRepository.saveServiceStateToDataStore(state)
}
}
Activity.kt
private fun observeDataFromViewModel() {
mainViewModel.readServiceStateFromRepository.observe(this, {state ->
Toast.makeText(this, "Service state changed to $state", Toast.LENGTH_SHORT).show()
// should get the new data when service stores it in onStartCommand but doesn't get it
// maybe because the service doesn't stores the data for some reason I am not aware of.
})
}
private fun handleClickListener() {
btn_start_service.setOnClickListener {
startForegroundService(serviceIntent)
}
}
btn_stop_service.setOnClickListener {
mainViewModel.saveServiceState(false)
stopService(serviceIntent)
}
}
ViewModel.kt
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val settingsRepository = (application.applicationContext as MainApplication).settingsRepository
val readServiceStateFromRepository = settingsRepository.getServiceStateFromDataStore.asLiveData()
fun saveServiceState(state: Boolean): Job {
return viewModelScope.launch(Dispatchers.IO) {
settingsRepository.saveServiceStateToDataStore(state)
}
}
}
Use the Messenger class to communicate with server https://developer.android.com/reference/android/app/Service.html#remote-messenger-service-sample
Or use a BroadcastReceiver to get service state from another process
class MainActivity : AppCompatActivity() {
private var receiver: TmpReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.view_main)
val intent = Intent(this, TmpService::class.java)
receiver = TmpReceiver()
val filter = IntentFilter().apply {
addAction("SERVICE_START")
addAction("SERVICE_STOP")
}
registerReceiver(receiver, filter)
startService(intent)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
receiver = null
}
}
class TmpService : Service() {
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
sendBroadcast("SERVICE_START")
}
override fun onDestroy() {
sendBroadcast("SERVICE_STOP")
super.onDestroy()
}
private fun sendBroadcast(action: String) {
Intent().also { intent ->
intent.action = action
sendBroadcast(intent)
}
}
}
class TmpReceiver: BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Log.d("TmpReceiver", "action=${p1?.action}")
}
}
You can register one more receiver into the service to ping it from the activity.
About the closest answer it works for a single process app only
I think somethings wrong about my code in TeamImplsTest, and i need advice :D
This is my code
API interface
interface API {
#GET("lookupteam.php")
fun getTeam(#Query("id") id: String): Call<TeamModel>
}
TeamPresenter
interface MatchPresenter {
fun loadTeamDetail(team_id: String)
}
TeamImpls
class TeamImpls(val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = RetrofitConfig().getApi().getTeam(team_id)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful()) {
val res = response.body()
res?.let { teamView.onSuccess(it) }
}
}
override fun onFailure(call: Call, t: Throwable) {
Log.e("PrevMatchFragment", t.toString())
}
})
}
}
TeamModel
data class TeamModel(
val teams: ArrayList
)
data class TeamModeLResult(
val idTeam: String,
val strTeam: String,
val strAlternate: String,
val strSport: String,
val strStadium: String,
val strTeamBadge: String
)
And
This my TeamImplsTest
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}
i got error
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
You're not mocking the API call as loadTeamDetail creates its own API instance.
To enable you to test the API call behaviour you could provide the API instance through your constructor, e.g.
class TeamImpls(private val api: API, private val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = api.getTeam(team_id)
This would then allow you to mock the api behaviour and verify the presenter calls the correct method when the call fails/succeeds, e.g.
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var api: API
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(api, teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
// Use retrofit-mock to create your mockResponse.
// See: https://github.com/square/retrofit/tree/master/retrofit-mock
Mockito.`when`(api.getTeam(teamId)).thenReturn(Calls.response(teams)
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}