I want to consume twitter streaming api in android.
I've used kotlin coroutines and retrofit.
Somehow in the third request i get an HTTP 420 ERROR (Enhance your calm)
I cannot understand why this happens. I am using kotlin coroutines.
Here's my code:
fun getStreamData(str: String) {
Log.d("debug", "Fetching data..")
coroutineScope.launch {
withContext(Dispatchers.Main) {
//Display loading animation in UI
_status.value = DataApiStatus.LOADING
}
try {
val listResult = ApiService().api!!.getTweetList(str).await()
while (!listResult.source().exhausted()) {
val reader = JsonReader(InputStreamReader(listResult.byteStream()))
// https://stackoverflow.com/questions/11484353/gson-throws-malformedjsonexception
reader.setLenient(true);
val gson = GsonBuilder().create()
val j = gson.fromJson<JsonObject>(reader, JsonObject::class.java)
Log.d("debug", "JSON: " + j.toString())
if (j.get("text") != null && j.getAsJsonObject("user").get("profile_image_url_https") != null && j.getAsJsonObject("user").get("name") != null){
val t = gson.fromJson<Tweet>(j, Tweet::class.java)
withContext(Dispatchers.Main) {
_status.value = DataApiStatus.DONE
// https://stackoverflow.com/questions/47941537/notify-observer-when-item-is-added-to-list-of-livedata
tweetsList.add(t)
_tweetsList.value = tweetsList
}
}
}
}
catch (e : JsonSyntaxException) {
Log.e("error", "JsonSyntaxException ${e.message}");
}
catch (e: Exception) {
Log.e("error", "ERROR ${e.message}")
}
}
}
This function is responsible to search the stream accordingly to str string which is a parameter.
Also, when the search parameter changes i cancel the current job and relaunch a new one with the actual search parameter.
fun cancelJob(){
Log.d("debug", "Cancelling current Job!")
coroutineScope.coroutineContext.cancelChildren()
}
What am i doing wrong? In the third request i get an HTTP 420 ERROR.
Here's the full code:
https://github.com/maiamiguel/RHO-Challenge
The 420 Enhance Your Calm status code is an unofficial extension by Twitter. Twitter used this to tell HTTP clients that they were being rate limited. Rate limiting means putting restrictions on the total number of requests a client may do within a time period.
Related
I want to stream twitter tweets continuously with twitter stream api with retrofit and without any 3rd party libraries. When i try to call the api " https://api.twitter.com/2/tweets/search/stream " I'm only getting the result first time. How to stream it?
I have finally achieved it using this snippet
override suspend fun streamTweets(): Flow<Resource<TweetResponseModel>> {
return flow {
val client: OkHttpClient = OkHttpClient().newBuilder().addInterceptor(
BasicAuthInterceptor(getAPIKey(), getAPISecretKey())
).build()
val request: Request = Request.Builder()
.url(TWITTER_STREAM_URL)
.method("GET", null)
.build()
val response: okhttp3.Response = client.newCall(request).execute()
val source = response.body?.source()
val buffer = Buffer()
while (!source!!.exhausted()) {
response.body?.source()?.read(buffer, 8192)
val data = buffer.readString(Charset.defaultCharset())
try {
val tweetResponseModel: TweetResponseModel =
Gson().fromJson(data, TweetResponseModel::class.java)
emit(Resource.Success(tweetResponseModel))
} catch (e: Exception) {
Log.e("jsonException", data)
}
}
}.flowOn(ioDispatcher)
}
I have written a code to fetch data from Cloud Firestore and am trying to implement the network calls using coroutines. I have tried to follow the official guides as much as possible, but since the functions have been left incomplete in those docs, I have made adjustments according to my requirements, but those might be the problem itself.
Here's the function which fetches the data:
suspend fun fetchHubList(): MutableLiveData<ArrayList<HubModel>> = withContext(Dispatchers.IO) {
val hubList = ArrayList<HubModel>()
val liveHubData = MutableLiveData<ArrayList<HubModel>>()
hubsListCollection.get().addOnSuccessListener { collection ->
if (collection != null) {
Log.d(TAG, "Data fetch successful!")
for (document in collection) {
Log.d(TAG, "the document id is ")
hubList.add(document.toObject(HubModel::class.java))
}
} else {
Log.d(TAG, "No such document")
}
}.addOnFailureListener { exception ->
Log.d(TAG, "get failed with ", exception)
}
if (hubList.isEmpty()) {
Log.d(TAG, "Collection size 0")
} else {
Log.d(TAG, "Collection size not 0")
}
liveHubData.postValue(hubList)
return#withContext liveHubData
}
And here is the ViewModel class which is calling this method:
class HubListViewModel(application: Application): AndroidViewModel(application) {
// The data which will be observed
var hubList = MutableLiveData<ArrayList<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch (Dispatchers.IO){
hubList = hubListDao.fetchHubList()
Log.d(TAG, "Array List fetched")
}
}
}
Using the tag messages I know that an empty list is being returned, which I know from another question of mine, is because the returned ArrayList is not in sync with the fetching operation, but I don't know why, since I've wrapped the whole function inside a with context block. Please tell me why the return and fetching is not being performed sequentially.
you should add this dependency "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.3". It allows you to use await() to replace callbacks.
suspend fun fetchHubList(): List<HubModel>? = try {
hubsListCollection.get().await().map { document ->
Log.d(TAG, "the document id is ${document.id}")
document.toObject(HubModel::class.java)
}.apply {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "Collection size is $size")
}
} catch (e: Exception) {
Log.d(TAG, "get failed with ", e)
null
}
Dispatchers.IO is not necessary since firebase APIs are main-safe
class HubListViewModel(application: Application): AndroidViewModel(application) {
val hubList = MutableLiveData<List<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch {
hubList.value = hubListDao.fetchHubList()
Log.d(TAG, "List fetched")
}
}
}
In swift there is a wonderful thing
if UIApplication.shared.canOpenURL(myUrl) {
// url can be opened
UIApplication.shared.open(myUrl) { success in
if !success {
//...
}
}
} else {
// and cannot
}
Is there an analogue in Kotlin?
Going off the documentation for canOpenURL(), it doesn't check if the URL is available, only if there's an app available that can handle its scheme.
On Android, the URL has to be wrapped in an Intent to be able to open it. You can then check if an app is available for the Intent by using the PackageManager.
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(url)
}
if (intent.resolveActivity(packageManager) != null) {
// url can be opened with startActivity(intent) or requireContext().startActivity(intent)
} else {
// ...
}
If this function is in a Fragment rather than Activity, prefix packageManager with requireContext()..
Edit:
You can check if it's possible to connect to the URL using a function like this (adapted from here):
suspend fun canConnect(url: String): Boolean = withContext(Dispatchers.IO) {
// We want to check the current URL
HttpURLConnection.setFollowRedirects(false)
val httpURLConnection = (URL(url).openConnection() as HttpURLConnection)
// We don't need to get data
httpURLConnection.requestMethod = "HEAD"
// Some websites don't like programmatic access so pretend to be a browser
httpURLConnection.setRequestProperty(
"User-Agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)"
)
// We only accept response code 200
return#withContext try {
httpURLConnection.responseCode == HttpURLConnection.HTTP_OK
} catch (e: IOException) {
false
} catch (e: UnknownHostException){
false
}
}
It has to be done asynchronously since you're making a connection, or else you risk an Application Not Responding error. So I made it a suspend function that you can call from a coroutine.
You can check if a URL is valid or not using patterns. See the sample function:
fun isValidUrl(url: String): Boolean {
val p = Patterns.WEB_URL
val m = p.matcher(url)
return m.matches()
}
Once the URL is validated, you can verify whether the device is able to connect to the URL or not using below method:
fun isAPIAvailable(c: Context, url:String): Boolean {
return try {
val ipAddr: InetAddress = InetAddress.getByName(url)
ipAddr.hostAddress != ""
} catch (e: Exception) {
false
}
}
Add isValidUrl():
fun String.isValidUrl(): Boolean = Patterns.WEB_URL.matcher(this).matches()
&& URLUtil.isValidUrl(url)
Then check:
val url = "www.myWebpage.com"
if (!url.isValidUrl()) {
// url can be opened
}else{
// and cannot
}
From sample doc Following code starts recording audio and streams to google cloud and receives responses. Everything works but I want to close straming after a certain condition is met.
if (mPermissionToRecord) {
val isFirstRequest = AtomicBoolean(true)
mAudioEmitter = AudioEmitter()
textView.setText("starting listener.")
// start streaming the data to the server and collect responses
val requestStream = mSpeechClient.streamingRecognizeCallable()
.bidiStreamingCall(object : ApiStreamObserver<StreamingRecognizeResponse> {
override fun onNext(value: StreamingRecognizeResponse) {
runOnUiThread {
when {
value.resultsCount > 0 -> mTextView.setText(
value.getResults(0).getAlternatives(
0
).transcript
)
else -> mTextView.setText(getString(R.string.api_error))
}
}
}
override fun onError(t: Throwable) {
//Log.e(TAG, "an error occurred", t)
textView.setText("an error occurred "+t.toString())
}
override fun onCompleted() {
//Log.d(TAG, "stream closed")
textView.setText("stream closed")
}
})
// monitor the input stream and send requests as audio data becomes available
mAudioEmitter!!.start { bytes ->
val builder = StreamingRecognizeRequest.newBuilder()
.setAudioContent(bytes)
// if first time, include the config
if (isFirstRequest.getAndSet(false)) {
builder.streamingConfig = StreamingRecognitionConfig.newBuilder()
.setConfig(
RecognitionConfig.newBuilder()
.setLanguageCode("en-US")
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
.setSampleRateHertz(16000)
.build()
)
.setInterimResults(true)
.setSingleUtterance(false)
.build()
}
// send the next request
requestStream.onNext(builder.build())
}
} else {
Log.e(TAG, "No permission to record! Please allow.")
}
AudioEmitter() is a audio recorder class. I tried to call:
mAudioEmitter?.stop()
mAudioEmitter = null
but that only stops the audio recording. I want to stop the audio streaming as well.
Calling mSpeechClient.shutdown() crashes the app.
How to stop SpeechClient bidiStreamingCall?
I've been trying to use khttp to send an .jpg file in an android activity but haven't been able to make it work.
fun sendImage(view: View) {
try {
var bmp = (imageView?.drawable as BitmapDrawable).bitmap
var bos = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.JPEG, 0, bos)
var response: Response? = null
findViewById<TextView>(R.id.image_desc).text = "Connecting to " + SERVER_URL;
try {
val job=GlobalScope.launch {
response = post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
}
findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
} catch (e: Exception) {
findViewById<TextView>(R.id.image_desc).text = "Connection failed - please check fields are valid"
findViewById<TextView>(R.id.image_desc).text = e.toString()
}
} catch (e: UnknownHostException) {
findViewById<TextView>(R.id.image_desc).text = "Unknown host :("
e.printStackTrace()
} catch (e: IOException) {
findViewById<TextView>(R.id.image_desc).text = "IO exceptiion :("
e.printStackTrace()
} catch (e: Exception) {
findViewById<TextView>(R.id.image_desc).text = "Other exception :("
e.printStackTrace()
}
}
As soon as i send the image, image_desc textView's text change to Image contains: null. I'm sure the server isn't the problem, since when I test it with this python code:
import requests
url=...
files = {'file': open('./test/cat.jpg', 'rb')}
r=requests.post(url,files=files)
print (r.text)
I get the desired response after a short delay. I've tried turning sendImage to a suspend func and writing job.join() but that crashes the app. How should fix this?
Try next code:
val job = GlobalScope.launch(Dispatchers.Main) {
val postOperation = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher
// blocking I/O operation
post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
}
response = postOperation.await() // wait for result of I/O operation without blocking the main thread
findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
}
Also add next line to app's build.gradle dependency:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
Note that GlobalScope is discouraged to use, to launch a coroutine use an instance of CoroutineScope, or existing instance like viewModelScope or lifecycleScope.
UPDATE:
The correct approach would be to use lifecycleScope in Activity:
lifecycleScope.launch { // uses Dispatchers.Main context
val response = withContext(Dispatchers.IO) { // change context to background thread
// blocking I/O operation
post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
}
findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
}