Live Data Observer called only once. It is not updating the data from server when api is called again to update UI.
Here is my activity:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onClick(){
callAPI(binding.edtEMail.text.toString(), binding.edtPasswords.text.toString())
}
}
private fun callAPI(userName: String, password: String) {
var factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LoginViewModel(
networkAvailable,
application,
getLoginUseCase,
userName,
password
) as T
}
}
val loginViewModel: LoginViewModel by lazy {
ViewModelProvider(this, factory)[LoginViewModel::class.java]
}
loginViewModel.loginMainEntity.observe(this, Observer {
when (it) {
is Resource.Success -> {
it.data?.let { it ->
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}
is Resource.Error -> {
it.message?.let { it ->
when (it) {
getString(R.string.invalid_login) -> {
Toast.makeText(this, R.string.error_user_pass, Toast.LENGTH_LONG)
.show()
}
getString(R.string.service_not_available) -> {
Toast.makeText(
this,
R.string.error_service_not_available,
Toast.LENGTH_LONG
)
.show()
}
else -> {
Toast.makeText(this, it, Toast.LENGTH_LONG)
.show()
}
}
}
}
}
})
}
}
And here is LoginViewModel:
class LoginViewModel constructor
(
private val networkAvailable: NetworkAvailable,
private val app: Application,
private val getLoginUseCase: GetLoginUseCase,
private val userName: String,
private val password: String
) : ViewModel() {
private val _loginMainEntity = MutableLiveData<Resource<LoginMainEntity>>()
val loginMainEntity: LiveData<Resource<LoginMainEntity>>
get() = _loginMainEntity
init {
loginValues()
}
private fun loginValues() {
viewModelScope.launch {
try {
_loginMainEntity.postValue(Resource.Loading())
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.unknown)))
}
try {
if (networkAvailable.isNetworkConnected()) {
try {
_loginMainEntity.postValue(getLoginUseCase.execute(userName, password))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
}
} else if (!networkAvailable.isNetworkConnected()) {
try {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api_network)))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api_network)))
}
}
} catch (e: Exception) {
try {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
}
}
}
}
}
In these below two functions I am getting referrerUrl and addId. I want both of them to be fetched in onCreate but don't know how because it is in try & catch block also the getGaid() function is not running without AsyncTask.
fun getreferrUrl() {
//to install referrer client
val referrerClient = InstallReferrerClient.newBuilder(this).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerResponse.OK -> {
// Connection established.
try {
val response: ReferrerDetails = referrerClient.installReferrer
val referrerUrl = response.installReferrer
// here we need referrerUrl out from this fuction
} catch (e: RemoteException) {
e.printStackTrace()
}
}
//
fun getGaid() {
AsyncTask.execute {
try {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(this)
val myId: String = if (adInfo != null) adInfo.id else null.toString()
//here we need myId out from this fuction
} catch (e: java.lang.Exception) {...}
}
}
In onCreate we need both of those strings.
// In onCreate
val url = "http://instbng.com?device_id=$device_id&
&kd_id=$kd_id&ref=$referrerUrl&gaid=$myId"
loadUrl(url)
Without coroutines, you can put the results in properties, and create a function that uses both properties and call it from both callbacks. I renamed your get... functions to fetch... since they are asynchronous. The word get in a function name implies they are synchronous.
private var referrerUrl: String? = null
private var myId: String? = null
override fun onCreate(bundle: SavedInstanceState?) {
super.onCreate(bundle)
//...
fetchReferrerUrl()
fetchGaId()
}
// proceeds with workflow if referrerUrl and myId are both available
private fun proceedIfReady() {
val referrer = referrerUrl ?: return
val id = myId ?: return
val url = "http://instbng.com?device_id=$device_id&kd_id=$kd_id&ref=$referrer&gaid=$idd"
loadUrl(url)
}
fun fetchReferrerUrl() {
val referrerClient = InstallReferrerClient.newBuilder(this).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerResponse.OK -> {
// Connection established.
try {
val response: ReferrerDetails = referrerClient.installReferrer
referrerUrl = response.installReferrer
proceedIfReady()
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
}
//... handle closed connection callback
}
}
private fun fetchGaId() {
AsyncTask.execute {
try {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(this)
runOnUiThread { // in a Fragment use view?.post
myId = if (adInfo != null) adInfo.id else null.toString()
proceedIfReady()
}
} catch (e: java.lang.Exception) {...}
}
}
I have two type of BT reader on of them is a Symbol CS3070 and the other one is a Datalogic DBT6400.
For the DBT6400 device I found an Android SDK with which the connection is very simple. But for the first one there is no supported SDK.
So I decided to connect to these device directly without SDK. Everything works fine,until the device disconnected because of sleep mode or because of I switch from one to another device.
In this case I get a lot of exception inside the while loop, because of the socket is closed. I have a tip for this why it's happen, maybe because of the coroutine, if it is possible.
UPDATE: So I found the problem, a put the catch block this line of code: reader=0, and there is only 1 error, so it's seems good. But I still don't know, is it a good solution?
My CustomReader class
class CustomReader(
private val deviceName: String,
onBarcodeRead: (barcode: String?) -> Unit
) : BarcodeReader(onBarcodeRead), CoroutineScope {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
private var pairedDevices: Set<BluetoothDevice>? = null
private var device: BluetoothDevice? = null
private lateinit var uuid: UUID
private var socket: BluetoothSocket? = null
private var connected: Boolean = false
private val localJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + localJob
override fun init(context: Context) {
pairedDevices = bluetoothAdapter.bondedDevices
}
override fun connect(address: String) {
try {
bluetoothAdapter.startDiscovery()
uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
device = pairedDevices?.firstOrNull { x -> x.name.startsWith(deviceName) }
if(device == null){
Timber.d("CustomReader: $deviceName: Device not found")
return
}
initSocket()
launch {
run()
}
} catch (e: Exception) {
Timber.e(e)
}
}
override fun disconnect() {
try {
socket?.close()
socket = null
} catch (e: IOException) {
Timber.d("CustomReader: $deviceName: Socket closed")
}
localJob.cancel()
}
override fun destroy() {
try {
socket?.close()
socket = null
} catch (e: IOException) {
Timber.d("CustomReader: $deviceName: Socket closed")
}
localJob.cancel()
}
override fun getAddress(): String? {
return device?.address
}
override fun isConnected(): Boolean {
return connected
}
private suspend fun run() {
try {
socket?.let { socket ->
socket.connect()
connected = true
val buffer = ByteArray(1024)
var read = 0
do {
try {
read = socket.inputStream.read(buffer)
val data = String(buffer, 0, read)
onBarcodeRead.invoke(data)
} catch (e: Exception) {
Timber.e(e)
}
} while (read > 0)
CoroutineScope()
}
}catch (e: Exception){
Timber.d("CustomReader: $deviceName Disconnected")
connected = false
}
}
fun initSocket(){
if(socket != null)
return
socket = device?.createRfcommSocketToServiceRecord(uuid)
bluetoothAdapter.cancelDiscovery()
}
fun closeSocket(){
try {
socket?.close()
}catch (e: Exception){
Timber.d("CustomReader: $deviceName: Socket closed")
}
}
}
**BletoothHandlar class:**
object BluetoothHandler {
#JvmStatic
private var currentReader: BarcodeReader? = null
#JvmStatic
private val barcodeLiveData = MutableLiveData<EventWrapper<String?>>()
#JvmStatic
fun getData(): LiveData<EventWrapper<String?>> = barcodeLiveData
init {
fixedRateTimer(
name = "blueooth-reconnect",
daemon = true,
initialDelay = 0L,
period = 5000
) {
if (currentReader == null) {
Timber.d("No reader device connected")
return#fixedRateTimer
}
if (currentReader?.isConnected() == true) {
Timber.d("reader connected, doing nothing")
} else if (currentReader?.isConnected() == false) {
Timber.d("reader not connected, reconnecting")
currentReader?.connect(currentReader!!.getAddress()!!)
}
}
}
#JvmStatic
fun connectToDevice(device: BluetoothDeviceCustom, context: Context): BarcodeReader? {
if (device.address == currentReader?.getAddress() && currentReader?.isConnected() == true) {
Timber.d("device already connected")
return currentReader
}
//ha változott a kiválasztott olvasó
if (device.address != currentReader?.getAddress()) {
currentReader?.disconnect()
}
val reader = createReader(device)
if (reader == null) {
currentReader?.disconnect()
} else {
reader.init(context)
reader.connect(device.address)
}
currentReader = reader
return reader
}
#JvmStatic
fun disconnect() {
currentReader?.disconnect()
currentReader?.destroy()
currentReader = null
}
#JvmStatic
fun getConnectedDevice(): BarcodeReader? {
return currentReader
}
#JvmStatic
private fun createReader(bluetoothDeviceCustom: BluetoothDeviceCustom): BarcodeReader? {
return CustomReader(bluetoothDeviceCustom.name) {
barcodeLiveData.postValue(EventWrapper(it))
}
/*return if (bluetoothDeviceCustom.name.startsWith("DBT6400")) {
DatalogicDBT6400 {
barcodeLiveData.postValue(EventWrapper(it))
}
} else if (bluetoothDeviceCustom.name.startsWith("CS3070")) {
CustomReader(bluetoothDeviceCustom.name) {
barcodeLiveData.postValue(EventWrapper(it))
}
} else {
null
}*/
}
}
So, here it is my solution, I don't think it is the best, but it works.
const val UUID_STRING = "00001101-0000-1000-8000-00805F9B34FB"
class CustomReader(
onBarcodeRead: (barcode: String?) -> Unit
) : BarcodeReader(onBarcodeRead), CoroutineScope {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
private var pairedDevices: Set<BluetoothDevice>? = null
private var device: BluetoothDevice? = null
private var socket: BluetoothSocket? = null
private val localJob: Job = Job()
private var deviceName = ""
private val uuid = UUID.fromString(UUID_STRING)
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + localJob
override fun init(context: Context) {
pairedDevices = bluetoothAdapter.bondedDevices
}
private val singleExecutor: ExecutorCoroutineDispatcher =
Executors.newFixedThreadPool(1).asCoroutineDispatcher()
override fun connect(address: String) {
try {
device = pairedDevices?.firstOrNull { x -> x.address == address }
if(device == null){
Timber.d("CustomReader: $deviceName: Device not found")
return
}
deviceName = device?.name ?: "N/A"
initSocket()
launch {
withContext(singleExecutor){
run()
}
}
} catch (e: Exception) {
Timber.e(e)
}
}
override fun disconnect() {
closeSocket()
localJob.cancel()
}
override fun getAddress(): String? {
return device?.address
}
override fun isConnected(): Boolean {
return socket?.isConnected ?: false
}
private suspend fun run() {
try {
socket?.let { socket ->
socket.connect()
val buffer = ByteArray(1024)
var read = 0
do {
try {
read = socket.inputStream.read(buffer)
val data = String(buffer, 0, read)
onBarcodeRead.invoke(data)
} catch (e: Exception) {
Timber.d("CustomReader: $deviceName Socket disconnected")
read = -1
closeSocket()
}
} while (read > 0)
}
}catch (e: Exception){
Timber.d("CustomReader : $deviceName Disconnected")
}
}
private fun closeSocket(){
try {
socket?.close()
}catch (e: IOException){
Timber.d("CustomReader: $deviceName Socket closed")
}
socket = null
}
private fun initSocket(){
if(socket != null)
return
socket = device?.createRfcommSocketToServiceRecord(uuid)
}
}
Server-side language: Python
Client-side language: Kotlin, Android
It receives data from different lines at one line.
class SampleActivity : AppCompatActivity() {
private val TAG = "SAMPLE_ACTIVITY_TAG"
private lateinit var editTextMessage: EditText
private val IPs = listOf("192.168.100.30")
private val PORTs = listOf(5050)
private val INDEX_SERVER = 0
private var socket: Socket? = null
private val IP = IPs[INDEX_SERVER]
private val PORT = PORTs[INDEX_SERVER]
private lateinit var outPrintWriter: PrintWriter
private val CONNECTION_TOKEN = "fab2bd65f67193d761c06b07a708d3232f0d8569"
private val TAG_CONNECT_START = "[CONNECT]"
private val TAG_CONNECT_END = "[/CONNECT]"
private val TAG_TRANSFER_START = "[TRANSFER]"
private val TAG_TRANSFER_END = "[/TRANSFER]"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
editTextMessage = findViewById(R.id.editTextMessage)
CoroutineScope(Dispatchers.IO).launch {
connect()
}
buttonSubmit.setOnClickListener {
if(editTextMessage.text.toString().trim().isEmpty()){
return#setOnClickListener
}
CoroutineScope(Dispatchers.IO).launch {
send(messageConverter(TAG_TRANSFER_START + editTextMessage.text.toString().trim() + TAG_TRANSFER_END))
}
}
}
override fun onDestroy() {
outPrintWriter.close()
super.onDestroy()
}
private fun messageConverter(text: String): String {
Log.w(TAG, "[CONVERTING] \nMessage: $text\nAscii: ${text.toASCII()}")
return text.toASCII() // Defined in extention.kt
}
private suspend fun connect() {
try {
Log.w(TAG, "[CONNECTING] Connecting to: $IP:$PORT")
socket = Socket(IP, PORT)
socket?.receiveBufferSize = 1024
socket?.soTimeout = 1 * 60 * 60
outPrintWriter = PrintWriter(socket!!.getOutputStream())
send(messageConverter(TAG_CONNECT_START + CONNECTION_TOKEN + TAG_CONNECT_END))
receive()
} catch (uHE: UnknownHostException) {
Log.e(TAG, "[ERROR] UnknownHostException, ${uHE.message}", uHE)
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] IOException, ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] Exception, ${e.message}", e)
}
}
private suspend fun send(text: String) {
try {
Log.w(TAG, "[SENDING] Message: $text")
outPrintWriter.print(text)
outPrintWriter.flush()
Log.w(TAG, "[SENDING] finished .")
clearMessageBox()
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] IOException, ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] IOException, ${e.message}", e)
}
}
private suspend fun receive() {
try {
val scanner = Scanner(
socket?.getInputStream()
)
val lines = ArrayList<String>()
var line: String?
while (scanner.hasNextLine()) {
Logger.w(TAG, "[RECEIVING] Scanner hasNext")
line = scanner.nextLine()
if (!line.isNullOrEmpty()) {
lines.add(line)
}
}
Logger.w(TAG, "[RECEIVING] Exit While loop : ${lines}")
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] Receive IOException error: ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] Receive Exception error: ${e.message}", e)
}
}
private suspend fun clearMessageBox() {
withContext(Main) {
editTextMessage.text.clear()
}
}
}
Logs:
[CONNECTING] Connecting to: 192.168.100.30:5050
[CONVERTING]
Message: [CONNECT]fab2bd65f67193d761c06b07a708d3232f0d8569[/CONNECT]
Ascii: 91,67,79,78,78,69,67,84,93,102,97,98,50,98,51,50,102,48,100,49,55,56,99,48,54,55,48,56,53,55,97,100,54,53,102,54,54,98,48,100,51,50,57,49,57,51,100,55,54,91,47,67,79,78,78,69,67,84,93
[SENDING] Message: 91,67,79,78,78,69,67,84,93,102,97,98,50,98,51,50,102,48,100,49,55,56,99,48,54,55,48,56,53,55,97,100,54,53,102,54,54,98,48,100,51,50,57,49,57,51,100,55,54,91,47,67,79,78,78,69,67,84,93
[SENDING] finished.
[RECEIVING] Scanner hasNext
[RECEIVING] Exit While loop : [OK1OK2]
Log: **[RECEIVING] Exit While loop :** shall be `[OK1, OK2]`
Server send messagesin every second twice
I just checked some questions and samples b, unfortunately in my case, no one solved the above issue
I created a boundary callback and a data source, the data source reads from the DB and the boundary callback is supposed to make the API calls and persist to the DB.
However, the overridden methods in boundary callback are never called, I don't know if there is a rule that says you can't mix both. But it is driving me crazy and I don't get it.
private val postsDataFactory = object: DataSource.Factory<String, Post>() {
override fun create(): DataSource<String, Post> {
return ItemDataSource(application)
}
}
private val postsBoundaryCallback = object: PagedList.BoundaryCallback<Post>(){
override fun onZeroItemsLoaded() {
super.onZeroItemsLoaded()
viewModelScope.launch {
try {
val posts = postsRepo.findPosts(mutableMapOf("limit" to "1"))
database.postDao().create(posts)
} catch (e: IOException) {
Log.d("NETWORK_ERROR", e.message)
} catch (e: Exception) {
Log.d("UNCAUGHT_ERROR", e.message)
}
}
}
override fun onItemAtEndLoaded(itemAtEnd: Post) {
viewModelScope.launch {
try {
val posts = postsRepo.findPosts(mutableMapOf(
"before" to itemAtEnd.id,
"limit" to "1"
))
database.postDao().create(posts)
} catch (e: IOException) {
Log.d("NETWORK_ERROR", e.message)
} catch (e: Exception) {
Log.d("UNCAUGHT_ERROR", e.message)
}
}
}
}
private val postPageConfig = PagedList
.Config
.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(10)
.build()
val postsLiveData = LivePagedListBuilder(
postsDataFactory,
postPageConfig
)
.setBoundaryCallback(postsBoundaryCallback)
.build()