I'm a newbie in android so sorry in advance, I'm inside a fun that use the user's input and translate it before sending the data to firebase Database, I think I'm doing something wrong with the variables because in the loop I can get the language text "en" but just after its null so the translation can't start..
println("SAVING DATABASE")
var theLangue:String? = null
var detect:String? = null
val languageIdentifier = LanguageIdentification.getClient()
languageIdentifier.identifyPossibleLanguages(userText)
.addOnSuccessListener { identifiedLanguages ->
for (identifedLanguage in identifiedLanguages) {
detect = identifedLanguage.languageTag
val confidence = identifedLanguage.confidence
println(" LANGUAGE DETECTED: $detect , LANGUAGE CONFIDENCE: $confidence")
}
theLangue = detect
}.addOnFailureListener {
println("problem cant translate!")
}
println("Translate pre build ${theLangue.toString()} ++ $theLangue") //todo NULL
val options = TranslatorOptions.Builder()
.setSourceLanguage(theLangue.toString()) // todo NULL
.setTargetLanguage(Locale.getDefault().displayLanguage)
.build()
val theTranslator = Translation.getClient(options)
val conditions = DownloadConditions.Builder()
.requireWifi()
.build()
theTranslator.downloadModelIfNeeded(conditions)
.addOnSuccessListener {
println("succes downloading models languages.. going to translate wait..")
theTranslator.translate(userText)
.addOnSuccessListener {
// Translation successful.
println("Succes translated text")
}
.addOnFailureListener { exception ->
// Error.
// ...
println("there is a problem failed to transalte !")
}
}
.addOnFailureListener { exception ->
// Model couldn’t be downloaded or other internal error.
// ...
println(exception.message)
println(exception.localizedMessage)
println("cant download languages models !")
}
The .addOnSuccessListener is an async call, which may not be executed right away.
A quick fix is to move the translate logic into the addOnSuccessListener callback in language identification.
`
...
languageIdentifier.identifyPossibleLanguages(userText)
.addOnSuccessListener { identifiedLanguages ->
for (identifedLanguage in identifiedLanguages) {
detect = identifedLanguage.languageTag
val confidence = identifedLanguage.confidence
println(" LANGUAGE DETECTED: $detect , LANGUAGE CONFIDENCE: $confidence")
}
theLangue = detect
val options = TranslatorOptions.Builder()
.setSourceLanguage(theLangue.toString()) // todo NULL
.setTargetLanguage(Locale.getDefault().displayLanguage)
.build()
val theTranslator = Translation.getClient(options)
...
}.addOnFailureListener {
println("problem cant translate!")
}
...
`
To improve the code readability, you could use the continueWithTask.
Related
With migration to kotlin, view model and recent changes in [kotlin test lib][1] I am working on issue with test.
I have a scenario:
request a web resource asynchronously
in case of error put the request in cache and update state with new pending request
All of this with help of kotlin flow and view model.
Scenario works well when executes on emulator, but fails when I run test for it. The issue is catch block of flow has not been triggered when error has thrown in flow.
Here is the code:
fun mintToken(to: String, value: Value, uri: String) {
logger.d("[start] mintToken()")
viewModelScope.launch {
repository.mintToken(to, value, uri)
.catch { it ->
if (it is TransactionException
&& it.message!!.contains("Transaction receipt was not generated after 600 seconds for transaction")) {
cacheRepository.createChainTx(to, value, uri) // TODO consider always put in pending cache and remove after it confirms as succeeded
val txReceipt = TransactionReceipt()
txReceipt.transactionHash = ""
emit(Response.Data(txReceipt))
} else {
emit(Response.Error.Exception(it))
}
}
.flowOn(Dispatchers.IO)
.collect {
logger.d(it.toString())
when (it) {
is Response.Data -> {
if (it.data.transactionHash.isEmpty()) {
state.update {
it.copy(
status = Status.MINT_TOKEN,
pendingTx = it.pendingTx + Transaction(to, value, uri)
)
}
}
}
is Response.Error.Message -> {
val errorMsg = "Something went wrong on mint a token with error ${it.msg}"
logger.d(errorMsg)
state.update {
val newErrors = it.errors + "Something went wrong on mint a token with error ${errorMsg}"
it.copy(status = Status.MINT_TOKEN, errors = newErrors)
}
}
is Response.Error.Exception -> {
logger.e("Something went wrong on mint a token ${to}, ${value}, ${uri}", it.error)
state.update {
val newErrors = it.errors + "Something went wrong on mint a token ${to}, ${value}, ${uri}"
it.copy(status = Status.MINT_TOKEN, errors = newErrors)
}
}
}
}
}
logger.d("[end] mintToken()")
}
#Throws(TransactionException::class)
override fun mintToken(to: String, value: Value, uri: String): Flow<Response<TransactionReceipt>> {
return flow {
throw TransactionException(
"Transaction receipt was not generated after 600 seconds for transaction",
"")
}
}
Test code for this is:
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Set the main coroutines dispatcher for unit testing.
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var subj: WalletViewModel
#Test
fun `when mintToken() is called with correct values, timeout exception is returned and pending tx are updated with new value`() = runTest {
val to = "0x6f1d841afce211dAead45e6109895c20f8ee92f0"
val url = "https://google.com"
val testValue = Value(
"Software Development",
BigInteger.valueOf(1000L),
BigInteger.valueOf(2000L),
false,
BigInteger.valueOf(0)
)
subj.mintToken(to, testValue, url)
assertThat(
"There is no pending transaction after mint a new token with timeout error",
subj.uiState.value.pendingTx.isNotEmpty()
)
}
Test code differs from dev code by replacing dispatcher in MainCoroutineRule and using kotlin construction runTest {}. How does it affect this case? Does issue case lays in some other place?
[1]: https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md
I am trying to get list of todos from database with livedata however, while debugging it always shows null for value. I have provided my files below.
My Dao:
#Query("SELECT * FROM todo_table WHERE IIF(:isCompleted IS NULL, 1, isCompleted = :isCompleted)")
fun getTodos(isCompleted: Boolean?): LiveData<List<Todo>>
My ViewModel:
private var _allTodoList = MutableLiveData<List<Todo>>()
var allTodoList: LiveData<List<Todo>> = _allTodoList
init {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
fun onFilterClick(todoType: Constants.TodoType) {
when (todoType) {
Constants.TodoType.ALL -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.COMPLETED -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(true)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.INCOMPLETE -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(false)
_allTodoList.postValue(list.value)
}
}
}
}
My MainActivity:
val allTodoList = viewModel.allTodoList.observeAsState()
allTodoList.value?.run {//value is always null
if (!isNullOrEmpty()) {
...
} else {
...
}
}
While debugging I found that allTodoList.value is always null however, when I manually run same query in app inspection I the get the desired results.
You can simplify your code, see if it works.
ViewModel only needs this:
val allTodoList: LiveData<List<Todo>> = todoRepository.getTodos(null)
MainActivity:
val allTodoList by viewModel.allTodoList.observeAsState()
if (!allTodoList.isNullOrEmpty()) {
...
} else {
...
}
You are not observing the LiveData you get from Room.
YourDao.getTodos() and LiveData.getValue() are not suspend functions, so you get the current value, which is null because Room has not yet fetched the values from SQLite.
A possible solution would be to set the todo type as a live data itself and use a switchMap transformation in the ViewModel :
private val todoType = MutableLiveData<Constants.TodoType>(Constants.TodoType.ALL)
val allTodoList: LiveData<List<Todo>> = androidx.lifecycle.Transformations.switchMap(todoType) { newType ->
val typeAsBoolean = when(newType) {
Constants.TodoType.ALL -> null
Constants.TodoType.COMPLETED -> true
Constants.TodoType.INCOMPLETE -> false
else -> throw IllegalArgumentException("Not a possible value")
}
// create the new wrapped LiveData
// the transformation takes care of subscribing to it
// (and unsubscribing to the old one)
todoRepository.getTodos(typeAsBoolean)
}
fun onFilterClick(todoType: Constants.TodoType) {
// triggers the transformation
todoType.setValue(todoType)
}
This is in fact the exact use case demonstrated in the reference doc
I want to make a dynamic controllers function but I don't sure how to work with it.
I just create a function below
fun <T> CollectionReference.getData(cls : Class<T>, id : String, callback : (d : T?, exception: Exception?)->Unit){
this.document(id).get().addOnSuccessListener(OnSuccessListener {
it?.let{
val v = it.toObject(cls)
callback(v, null)
}
}).addOnFailureListener(OnFailureListener{
callback(null, it);
})
}
want to use the function like:
val userRef = FirebaseFirestore.getInstance().collection("users")
val citiesRef = FirebaseFirestore.getInstance().collection("cities")
userRef.getData(cls = User::class.java, id = "123"){ user, exception ->
user?.let{
// user data for use
}
exception?.let{
it.printStacktrace()
}
}
citiesRef.getData(cls = City::class.java, id = "abc"){ city, exception ->
city?.let{
// city data for use
}
exception?.let{
it.printStacktrace()
}
}
If there's a better way to use please let me know.
Thanks in advance :)
I have a Collection of Units, each unit have many fields. One of those fields is A map called Settings. The settings is <String,Any>: A->true, B->false, C->"Hello" etc.
I wish to update one of them, lets say I wish to set C to "World".
My code:
suspend fun updateData(unitID: String): Boolean = suspendCoroutine { cont ->
val firestore = FirebaseFirestore.getInstance()
firestore.collection("Units").document(unitID).get().addOnCompleteListener { it1 ->
if (it1.isSuccessful) {
val settings = it1.result.get("Settings") as? HashMap<String, Any>
if (settings != null) {
settings["C"] = "World"
val map = hashMapOf<String, Any>()
map["Settings"] = settings
firestore.collection("Units").document(unitID).update(map).addOnCompleteListener { it2->
if (it2.isSuccessful) cont.resume(true)
else cont.resumeWithException(it2.exception!!)
}
}
}
else cont.resumeWithException(it1.exception!!)
}
}
What am I doing? I am getting the map, updating the value and setting it back.
My question, is that the correct approach, can I just set the value without reading the data first?
its fine to update the data without reading it. it would save your read query limit. so don't have to do that unless there is a actual need to do.
Suggestion:
Don't name it1, it2 like that. use meaning full names.
Found it:
suspend fun updateData(unitID: String): Boolean = suspendCoroutine { cont ->
val firestore = FirebaseFirestore.getInstance()
val map = mapOf("Settings.C" to "World")
firestore.collection("Units").document(unitID).update(map).addOnCompleteListener { updateData ->
if (updateData.isSuccessful) cont.resume(true)
else cont.resume(false)
}
}
I am making android app and I wants save configuration in Android DataStore. I have created a class and the values from EditText are correct save to DataStore. I using tutorial from YouTube: https://www.youtube.com/watch?v=hEHVn9ATVjY
I can view the configuration in the config view correctly (textview fields get the value from the datastore):
private fun showConfigurationInForm(){
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainViewModel.readMqttAddressFlow.observe(this) { mqqtAdress ->
binding.conMqttAddress.setText(mqqtAdress)
}
}
This function show actual config in EditText, and this is great
But the config I will use to connect to MQTT Server, and how can I save the config to Varchar and use to another function?
I create var in class:
class ConfigurationActivity : AppCompatActivity() {
private lateinit var binding: ActivityConfigurationBinding
private lateinit var mainViewModel: MainViewModel
var variMqttAddress = ""
(...)
And in function getValueFromDatastoreAndSaveToVar I want to get and save values from DataStore to variable variMqttAddress
private fun getValueFromDatastoreAndSaveToVar(){
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainViewModel.readMqttAddressFlow.observe(this) { mqqtAdress ->
variMqttAddress = mqqtAdress
}
}
but it doesn't work. when debugging I have an empty value in var
Log.d(TAG, "variMqttAddress:: $variMqttAddress")
___________
2021-02-16 12:42:20.524 12792-12792 D/DEBUG: variMqttAddress::
Please help
When using flows with DataStore, value will be fetched asynchronously meaning you wont have the value right away, try printing log inside observe method and then create your MQttClient with the url
private fun getValueFromDatastoreAndSaveToVar(){
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainViewModel.readMqttAddressFlow.observe(this) { mqqtAdress ->
variMqttAddress = mqqtAdress
//varImqttAddress will be available at this point
Log.d(TAG, "variMqttAddress:: $variMqttAddress")
val mqttClient = MqttAsyncClient(varImqttAddress, clientId, MemoryPersistence())
}
}
other way is to use, collect/first on flows for blocking get but it requires to be inside a coroutinescope
Quick Tip: I think you can initialise mainViewModel globally once and access it in all methods instead of reassigning them in each
method. Seems redundant
UPDATE
If you have multiple values coming from different LiveData instances, then you can create a method something like validateParatmers(), which will have checks for all the parameters before creating instance like
private fun getValueFromDatastoreAndSaveToVar(){
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainViewModel.readMqttAddressFlow.observe(this) { mqqtAdress ->
variMqttAddress = mqqtAdress
Log.d(TAG, "variMqttAddress:: $variMqttAddress")
validateParametersAndInitMqtt() //add checks after observing ever livedata
}
mainViewModel.readMqttPortFlow.observe(this) {mqttPort ->
variMqttPass = mqttPort.toString()
validateParametersAndInitMqtt()
}
mainViewModel.readMqttUserFlow.observe(this) { mqttUser ->
variMqttUser = mqttUser
validateParametersAndInitMqtt()
}
mainViewModel.readMqttPassFlow.observe(this) { mqttPass ->
variMqttPass = mqttPass
validateParametersAndInitMqtt()
}
}
private fun validateParametersAndInitMqtt(){
if(variMqttAddress.isEmpty() || variMqttPass.isEmpty()
|| variMqttUser.isEmpty() || variMqttPass.isEmpty()){
//if any one is also empty, then don't proceed further
return
}
//create socket instance here, all your values will be available
}
Thank you for your help
I did not add earlier that in addition to the address of the MQQT server in the configuration, it also stores the port, user and password.
I think I am doing something wrong, in every YouTube tutorial it is shown how to "download" one configuration parameter. My function that retrieves data now looks like this:
private fun getValueFromDatastoreAndSaveToVar(){
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainViewModel.readMqttAddressFlow.observe(this) { mqqtAdress ->
variMqttAddress = mqqtAdress
Log.d(TAG, "variMqttAddress:: $variMqttAddress")
}
mainViewModel.readMqttPortFlow.observe(this) {mqttPort ->
variMqttPass = mqttPort.toString()
}
mainViewModel.readMqttUserFlow.observe(this) { mqttUser ->
variMqttUser = mqttUser
}
mainViewModel.readMqttPassFlow.observe(this) { mqttPass ->
variMqttPass = mqttPass
}
}
in the repository class, I create a flow for each value
//Create MQTT Address flow
val readMqttAddressFlow: Flow<String> = dataStore.data
.catch { exception ->
if(exception is IOException){
Log.d("DataStore", exception.message.toString())
emit(emptyPreferences())
}else {
throw exception
}
}
.map { preference ->
val mqqtAdress = preference[PreferenceKeys.CON_MQTT_ADDRESS] ?: "none"
mqqtAdress
}
//Create MQTT Port flow
val readMqttPortFlow: Flow<Int> = dataStore.data
.catch { exception ->
if(exception is IOException){
Log.d("DataStore", exception.message.toString())
emit(emptyPreferences())
}else {
throw exception
}
}
.map { preference ->
val mqqtPort = preference[PreferenceKeys.CON_MQTT_PORT] ?: 0
mqqtPort
}
(.....)
now the question is am I doing it right?
now how to create MQttClient only when I have all parameters in variables?
can do some sleep of the function that is supposed to create the MQQTClient until the asychnronic function assigns values to variables?