I have an app that communicates with a server during sockets.
first I did this:
val sock = Socket("192.168.1.108", 5000)
and the app crashed because of the error: "android.os.NetworkOnMainThreadException", I read about it and I found a solution for that error and the solution is to create a syncTask as an inner class, and this is what I did:
class randomChat : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.chat_show)
HandleServer().execute()
}
inner class HandleServer: AsyncTask<String, String, String>() {
override fun doInBackground(vararg p0: String?): String {
val sock = Socket("192.168.1.108", 5000)
sock.getInputStream()
sock.use {
it.outputStream.write("hello socket world".toByteArray())
}
return "Good"
}
}
}
and that fixed the error, But did not fix my needs...
basically, my needs are to have a conversion between the server and the user who use the app, the user will have an editText view and a button to send the data to the server and a textView that always change based on server data.
So:
What I need to have is:
open socket that receives data all the time from the server and updates a view in the activity
I need to be able to have an editText in the activity that by user click it's send data to the server (with the socket)
Thank you very much !!!!!
You can implement on ongoing socket like this, and just call the sendDataToNetwork() method for the editText
Example: Android bi-directional network socket using AsyncTask
There are better ways to manage threads or use libraries like RxJava, but for a basic simple implementation the above should work.
Instead of bothering with the AsyncTask, I highly recommend you to use coroutines instead:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.chat_show)
launch (CommonPool) {
val sock = Socket("192.168.1.108", 5000)
sock.use {
it.outputStream.write("hello socket world".toByteArray())
withContext(UI) {
// update the view
}
// more socket ops
withContext(UI) {
// update the view again
}
}
}
}
Related
In my app I start a WebSocketWorker tasks that runs periodically every 15 minutes. As the name implies, it contains a WebSocket for listening to a socket in the background:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val work = PeriodicWorkRequestBuilder<WebSocketWorker>(15, TimeUnit.MINUTES).build()
workManager.enqueueUniquePeriodicWork("UniqueWebSocketWorker", ExistingPeriodicWorkPolicy.KEEP, work)
}
The WebSocketWorker contains the following logic:
#HiltWorker
class WebSocketWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
inner class MyWebSocketListener : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d("The message sent is %s", text)
// do sth. with the message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.localizedMessage?.let { Timber.e("onFailure: %s", it) }
response?.message?.let { Timber.e("onFailure: %s", it) }
}
}
override suspend fun doWork(): Result {
try{
// code to be executed
val request = Request.Builder().url("ws://***.***.**.***:8000/ws/chat/lobby/").build()
val myWebSocketListener = MyWebSocketListener()
val client = OkHttpClient()
client.newWebSocket(request, myWebSocketListener)
return Result.success()
}
catch (throwable:Throwable){
Timber.e("There is a failure")
Timber.e("throwable.localizedMessage: %s", throwable.localizedMessage)
// clean up and log
return Result.failure()
}
}
}
As you can see, in the Worker class I set the WebSocket and everything is fine. Listening to the socket works.
Now, I also want to add the "sending of messages" functionality to my app. How can I reuse the websocket created in WebSocketWorker? Can I pass input data to the WebSocketWorker that runs in the background ?
Let's say I have a EditText for typing the message and a Button to send the message with a setOnClickListener attached like this:
binding.sendButton.setOnClickListener {
// get message
val message = binding.chatMessageEditText.text.toString()
// check if not empty
if(message.isNotEmpty()) {
// HOW CAN I REUSE THE WEBSOCKET RUNNING PERIODICALLY IN THE BACKGROUND?
// CAN I PASS THE MESSAGE TO THAT WEBSOCKET ?
// OR SHOULD I CREATE A DIFFERENT WORKER FOR SENDING MESSAGES (e.g.: a OneTimeRequest<SendMessageWorker> for sending messages ?
}
}
From the documentation, I know that you need to build Data objects for passing inputs and so on but there was no example which showcased how to pass input to a worker running periodically in the background.
My experience is saying that you can. Basically you "can't" interact with the worker object via the API. It is really annoying.
For example, with the JS you have the option to get a job and check the parameters of the job. There is no such option with the work. For example, I want to check what is the current state of the restrictions - what is satisfied, what is not. Nothing like this. You can just check states, cancel and that is almost all.
My suggestions is that it is because the WorkManager is a "facade/adapter" over other libraries like JS. It has it's own DB to restore JS jobs on device restart and stuff like this, but beside that if you want to interact with the internals I guess it was just too complicated for them to do so they just skipped.
You can just inject some other object and every time the work can ask it for it's data. I don't see other option.
The answer for checking internet was posted back in 2014 in this post: https://stackoverflow.com/a/27312494/12359431
However, in one of the answer, there is this piece of code
fun hasInternetConnection(): Single<Boolean> {
return Single.fromCallable {
try {
// Connect to Google DNS to check for connection
val timeoutMs = 1500
val socket = Socket()
val socketAddress = InetSocketAddress("8.8.8.8", 53)
socket.connect(socketAddress, timeoutMs)
socket.close()
true
} catch (e: IOException) {
false
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
I have tried it by implementing the code above to my code at the bottom. However, it just crashes and I could not get any finding of the error as to why the app crash.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/* Initialise Azure Service Adapter */
AzureServiceAdapter.Initialize(this)
hasInternetConnection().subscribe{hasInternet->
/*Call database and check phone number*/
Log.i("Logger", "Connected")}
/* Authentication */
authUser()
}
}
This is my implementations
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
Is there anything I'm lacking or I shouldn't add to my MainActivity File? Or a clue as to why my kotlin app crash ?
That's because you cant call this on main Thread.
Check if you added Internet permission in Manifest.
hasInternetConnection()
.subscribeOn(Schedulars.io())
.observeOn(AndroidSchedulars.mainThread()).subscribe{hasInternet->
/*Call database and check phone number*/
Log.i("Logger", "Connected")}
I know that an AsyncTask can be run only once. I know a way around that, but I need a variable from the AsyncTask that uses complicated(?) processes. This is my code for calling the AsyncTask
val thr=NewTask()
thr.delegate = this
button.setOnClickListener {
thr.execute()
}
NewTask.doOnBackground() is just a normal method sending the request to the URL. onPostExecute() is a bit different:
public override fun onPostExecute(result: String?) {
//super.onPostExecute(result)
delegate!!.processFinish(result!!)
}
with delegate being a variable of AsyncResponse? which is an interface containing processFinish abstract method taking a string and returning nothing.
My question is, how can I run the AsyncTask repeatedly while still getting the response? Thanks in advance.
Finally, I settled on using coroutines with this. Coroutines are easy to use, much easier than AsyncTask. I don't know why I was scared of them. Here is the code I used:
class CoRoutine{
suspend fun httpGet(url: String = "https://boogle.org"): String {
val arr = ArrayList<String>()
withContext(Dispatchers.IO) {
val url = URL(url)
with(url.openConnection() as HttpURLConnection) {
requestMethod = "GET" // optional default is GET
//arr.add(responseCode)
inputStream.bufferedReader().use {
it.lines().forEach { line ->
//println(line)
arr.add(line as String)
}
}
}
}
return arr.get(0)
}
}
I am developing an Android app using Cloud Firestore to store data. This is how I set data to the database:
private fun setData() {
val task = UploadDataTask()
task.setOnUploadFinishedListener(object: UploadDataTask.OnUploadFinishedListener{
override fun uploadFinished() {
// Do something
}
override fun uploadFailed() {
// Do something
}
})
}
private class UploadDataTask: AsyncTask<Void, Void, Void>() {
private var onUploadFinishedListener: OnUploadFinishedListener? = null
fun setOnUploadFinishedListener(listener: OnUploadFinishedListener) {
onUploadFinishedListener = listener
}
override fun doInBackground(vararg params: Void?): Void? {
val map = hashMapOf(
UID to firebaseUser.uid
)
firebaseFirestore.collection(USERS)
.document(firebaseUser.uid)
.set(map)
.addOnSuccessListener {
if(onUploadFinishedListener != null)
onUploadFinishedListener!!.uploadFinished()
}
.addOnFailureListener {
if(onUploadFinishedListener != null)
onUploadFinishedListener!!.uploadFailed()
}
return null
}
interface OnUploadFinishedListener {
fun uploadFinished()
fun uploadFailed()
}
}
This works great, but there is one exception. When I want to load data to the Firestore, but there is no connection to the internet, neither the onSuccessListener nor the onFailureListener gets called. I know that this is because they only get called when the data is written to the Firestore. But I don't know of any other way to check if there is a connection or not. For example, when I want to show a progress dialog until the data is successfully written to the Firestore, it would not dismiss if there was no connection. So how can I check that?
First thing first. The Cloud Firestore client already runs all network operations in a background thread. This means that all operations take place without blocking the main thread. Putting it in an AsyncTask does not give any additional benefits.
For example, when I want to show a progress dialog until the data is successfully written to the Firestore, it would not dismiss if there was no connection.
Simply by displaying the ProgressDialog once you call setData() method and dismiss it in onSuccess(). Since this method is called only when the data is successfully written on Firebase servers, that's the right place to use it.
Furthermore, if you want to have the same behaviour when you read data then you should use isFromCache() method like explained in my answer from the following post:
Listen For MetaData Changes in Firebase Firestore database
What I am trying to make is a local chat app (i.e, over local network, i.e. without internet only i.e. hotspot wifi based on android).I studied about sockets and tried to implement using https://github.com/socketio/socket.io-client-java I tried to make use of mainly socket.emit and socket.on. Both of my devices are connected on local network so can I do this
class MainActivity : AppCompatActivity()
{
var TAG = "TCPClient"; //For debugging, always a good idea to have defined
var serverIp = "192.168.0.102";
var startTime = 0L;
var serverPort = 5000;
lateinit var socket:Socket
var onMessageReceived=object:Emitter.Listener
{
override fun call(vararg args: Any?)
{
runOnUiThread {
editText2.setText(args[0].toString())
Toast.makeText(this#MainActivity,"called",Toast.LENGTH_SHORT).show()
}
}
}
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var SOCKET_URL="http://192.168.0.102"
socket= IO.socket(SOCKET_URL)
button.setOnClickListener {
socket.emit("messageSent",editText.text.toString())
Toast.makeText(this#MainActivity,"button",Toast.LENGTH_SHORT).show()
}
socket.on("messageSent",onMessageReceived)
socket.connect()
}
override fun onDestroy()
{
socket.disconnect()
super.onDestroy()
}
}
What I was trying whatever is the text written in editext1(in one phone with ip 192.68.0.102) is displayed in edittex2 (of both devices (one with ip 192.168.0.102 and the other devices on local network(192.168.0.100) since I ran the same app on both devices)) when button is pressed.What I am doing wrong? I would have made this app by hosting rest service on one of the devices and accessing it from all the connected devices in the network but the main problem I was facing is that if I do so I would have to request the rest api again and again in a loop in a thread to check for a new message.
Don't all the connected devices listen when we use socket.on?