I am using TheMovieDB API to call the JSON Response of the popular movies and put it into a ScrollView. I have ensured that I have done all the neccessary steps to get the API. However it does not work and does not display anything. If I use another API: "https://jsonplaceholder.typicode.com/posts", it works and the JSON data is displayed into the ScrollView.
Network Utils:
companion object {
private val TAG: String = NetworkUtils::class.java!!.simpleName
private val JSON_RESPONSE_URL = "https://api.themoviedb.org/3/movie/popular?api_key=myapikeyishere"
private val TYPE_SINGLE = 1
private val TYPE_ALL = 0
/**
* Builds the URL used to talk to the weather server using a location. This location is based
* on the query capabilities of the weather provider that we are using.
*
* #param locationQuery The location that will be queried for.
* #return The URL to use to query the weather server.
*/
fun buildURLSingleType(id: Int): URL {
return buildUrl(
TYPE_SINGLE,
id
)
}
fun buildURLAll(): URL {
return buildUrl(
TYPE_ALL,
0
)
}
private fun buildUrl(type: Int, id: Int): URL {
var uri = Uri.parse(JSON_RESPONSE_URL).buildUpon()
if (type == TYPE_SINGLE) {
uri.appendPath("1").build()
}
val builtUri = uri.build()
var url: URL? = null
try {
url = URL(builtUri.toString())
} catch (e: MalformedURLException) {
e.printStackTrace()
}
Log.v(TAG, "Built URI " + url!!)
return url
}
/**
* This method returns the entire result from the HTTP response.
*
* #param url The URL to fetch the HTTP response from.
* #return The contents of the HTTP response.
* #throws IOException Related to network and stream reading
*/
#Throws(IOException::class)
fun getResponseFromHttpUrl(url: URL): String? {
val urlConnection = url.openConnection() as HttpURLConnection
try {
val `in` = urlConnection.getInputStream()
val scanner = Scanner(`in`)
scanner.useDelimiter("\\A")
val hasInput = scanner.hasNext()
return if (hasInput) {
scanner.next()
} else {
null
}
} catch (ex: Exception) {
Log.d(TAG, ex.toString())
} finally {
urlConnection.disconnect()
}
return null
}
}
The Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_network)
}
override fun onResume() {
super.onResume()
val scope = CoroutineScope(Job() + Dispatchers.IO)
var nwMutipleItemsJob = scope.async(Dispatchers.IO)
{
var nwURL = NetworkUtils.buildURLAll()
val response = NetworkUtils.getResponseFromHttpUrl(nwURL)
response
}
scope.async(Dispatchers.Default)
{
var response = nwMutipleItemsJob.await()
var jsonResponse = JSONArray(response)
var msg = "$response\n\n"
for(i in 0 until jsonResponse.length())
{
var jsonItem = jsonResponse.getJSONObject(i)
// var userid = jsonItem.getInt("userId")
// var id = jsonItem.getInt("adult")
// var title = jsonItem.getString("title")
// var body = jsonItem.getString("body")
// msg += "item $i\n\nid = \n = $id\n"
}
withContext(Dispatchers.Main)
{
tvJSONMultipleItemDisplay.text = msg
}
}
}
}
layout activity:
<ScrollView
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="#+id/tvJSONMultipleItemDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
/>
</ScrollView>
However it does not work and does not display anything. ? Did you find any error ?.
Please add logs in your getResponseFromHttpUrl and onResume that will help you to find and fix the issue.
How to debug ?.
Ref : https://stackoverflow.com/a/62019186/9909365
On the Android, the log of device display on Tab Logcat, not on console as the Chrome. You can see the log in this tab, and remember build your App in the debug mode.
Edit: If you want to see all the log, you can switch the option Show only Application selected to No filter
For more information, you can find in this link
How to add logs ?
Ref : https://developer.android.com/studio/debug/am-logcat
The Logcat window in Android Studio displays system messages, such as when a garbage collection occurs, and messages that you added to your app with the Log class. It displays messages in real time and keeps a history so you can view older messages.
To display just the information of interest, you can create filters, modify how much information is displayed in messages, set priority levels, display messages produced by app code only, and search the log. By default, logcat shows the log output related to the most recently run app only.
When an app throws an exception, logcat shows a message followed by the associated stack trace containing links to the line of code.
As of Android Studio 2.2, the Run window also displays log messages for the current running app. Note that you can configure the logcat output display, but not the Run window.
Write log messages
Ref : https://developer.android.com/studio/debug/am-logcat#WriteLogs
The Log class allows you to create log messages that appear in logcat. Generally, you should use the following log methods, listed in order from the highest to lowest priority (or, least to most verbose):
Log.e(String, String) (error)
Log.w(String, String) (warning)
Log.i(String, String) (information)
Log.d(String, String) (debug)
Log.v(String, String) (verbose)
Related
I want to show a loading indicator at the beginning of the first api call when list of items is being fetched from the server. Everything is okay if data is fetched successfully. That means the loading indicator got invisible if data loaded successfully. The code is given bellow.
must use this code in view model
protected open fun <T> Single<T>.asPageLoadEventSource(eventId: Int = Random.nextInt()): Single<T> {
var emitter: SingleEmitter<Any>? = null
val single = Single.create<Any> { emitter = it }
pageLoadEventSingles[eventId] = single
return this.doOnSuccess { emitter?.onSuccess(it as Any) }
.doOnError { emitter?.tryOnError(it) }
}
view model
private fun getIncomingStiDest() {
val queries = mapOf(
"last_status" to stiDest,
"type" to incoming
)
getStiDestActionLiveData.value = Resource(Status.LOADING)
getDashboardCounterUseCase.execute(queries).asPageLoadEventSource().subscribe({
getStiDestActionLiveData.value = Resource(Status.SUCCESS, it)
}, {
getStiDestActionLiveData.value = Resource(Status.ERROR, it)
}).collect()
}
I am a newbie to android coroutines my requirements
Need to upload 20 images
Keep track of upload(at least when it gets finished I need to hide progressBar of each image)
After uploading all the images need to enable a "next" button also
Here is my try:
private fun startUploading(){
// Get AWS data
val accessKey = sharedPreferences.getString(getString(R.string.aws_access_key), "").toString()
val secretKey = sharedPreferences.getString(getString(R.string.aws_secret_key), "").toString()
val bucketName = sharedPreferences.getString(getString(R.string.aws_bucket_name), "").toString()
val region = sharedPreferences.getString(getString(R.string.aws_region), "").toString()
val distributionUrl = sharedPreferences.getString(getString(R.string.aws_distribution_url), "").toString()
var totalImagesNeedToUpload = 0
var totalImagesUploaded = 0
CoroutineScope(Dispatchers.IO).launch {
for (i in allCapturedImages.indices) {
val allImageFiles = allCapturedImages[i].viewItem.ImageFiles
totalImagesNeedToUpload += allImageFiles.size
for (j in allImageFiles.indices) {
CoroutineScope(Dispatchers.IO).launch {
while (true) {
val internetActive = utilsClassInstance.hasInternetConnected()
if (internetActive){
try {
val file = allImageFiles[j]
if (!file.uploaded) {
// Upload the file
val cfUrl = utilsClassInstance.uploadFile(file.imageFile, accessKey, secretKey, bucketName, region, distributionUrl)
// Set the uploaded status to true
file.uploaded = true
file.uploadedUrl = cfUrl
// Increment the count of total uploaded images
totalImagesUploaded += 1
// Upload is done for that particular set image
CoroutineScope(Dispatchers.Main).launch {
mainRecyclerAdapter?.uploadCompleteForViewItemImage(i, j, cfUrl)
// Set the next button enabled
if (totalImagesUploaded == totalImagesNeedToUpload){
binding.btnNext.isEnabled = true
}
}
break
}else{
totalImagesUploaded += 1
break
}
} catch (e: Exception) {
println(e.printStackTrace())
}
}
}
CoroutineScope(Dispatchers.Main).launch {
if (totalImagesUploaded == totalImagesNeedToUpload){
updateProgressForAllImages()
binding.btnNext.isEnabled = true
}
}
}
}
}
}
}
fun uploadFile(file: File, accessKey:String, secretKey:String, bucketName: String, region:String, distributionUrl: String): String{
// Create a S3 client
val s3Client = AmazonS3Client(BasicAWSCredentials(accessKey, secretKey))
s3Client.setRegion(Region.getRegion(region))
// Create a put object
val por = PutObjectRequest(bucketName, file.name, file)
s3Client.putObject(por)
// Override the response headers
val override = ResponseHeaderOverrides()
override.contentType = "image/jpeg"
// Generate the url request
val urlRequest = GeneratePresignedUrlRequest(bucketName, file.name)
urlRequest.responseHeaders = override
// Get the generated url
val url = s3Client.generatePresignedUrl(urlRequest)
return url.toString().replace("https://${bucketName}.s3.amazonaws.com/", distributionUrl)
}
There are total "n" images that I need to upload
every image is getting uploaded in different Coroutine because I need to do the parallel upload
The whole question is how to know that all the images are uploaded and enable a next button?
Your code seems very unstructured. You have an infinite loop checking for network availability. You have a nested loop here to upload images (Why?). You are creating a lot of coroutine scopes and have no control over them
Based on the 3 requirements that you mentioned in the question, you can do something like this:
val imagesToUpload: List<File> = /* ... */
var filesUploaded = 0
lifecycleScope.launchWhenStarted {
coroutineScope { // This will return only when all child coroutines have finished
imagesToUpload.forEach { imageFile ->
launch { // Run every upload in parallel
val url = utilsClassInstance.uploadFile(file.imageFile, ...) // Assuming this is a non-blocking suspend function.
filesUploaded++
// Pass the `url` to your adapter to display the image
binding.progressBar.progress = (filesUploaded * 100) / imagesToUpload.size // Update progress bar
}
}
}
// All images have been uploaded at this point.
binding.btnNext.enabled = true
}
Ideally you should have used a viewModelScope and the upload code should be in a repository, but since you don't seem to have a proper architecture in place, I have used lifecycleScope which you can get inside an Activity or Fragment
I have recently started working on Android, Kotlin and MPAndroidChart. I am developing an Android application that receives data from a bluetooth server and the data obtained should be plotted in real time using MPAndroidChart.
Here is the Kotlin code:
package com.example.flowsensor
import ...
class ConnectionActivity:AppCompatActivity() {
companion object{
val TAG = "FlowSensor"
val APP_NAME = "FlowSensor"
var myUUID: UUID = UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
var mBluetoothSocket: BluetoothSocket? = null
lateinit var mProgress: ProgressDialog
lateinit var mBluetoothAdapter: BluetoothAdapter
var mIsConnected: Boolean = false
lateinit var mAddress: String
lateinit var editText:EditText
lateinit var mChart:LineChart
var xVal:Int = 0
var yVal:Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_connection)
mChart = findViewById<LineChart>(R.id.line_chart)
receive_button.setOnClickListener{
ReceiveData()
}
}
//1 parameters missing
private fun mAddEntry() {
Log.d(TAG, "yVal : $yVal")
var mData = mChart.data
if(mData != null){
var mDataSet = mData.getDataSetByIndex(0)
if(mDataSet == null){
mDataSet = createDataSet()
mData.addDataSet(mDataSet)
}
var mEntry:Entry = Entry( xVal.toFloat(), yVal.toFloat())
xVal++
mData.addEntry(mEntry, 0)
//Notify chart data has changed
mChart.notifyDataSetChanged()
//Limit no of visible entries
// mChart.setVisibleXRange(1f, 6f)
mChart.setVisibleXRangeMaximum(6f)
//Scroll to the last entry
mChart.moveViewToX(xVal.toFloat())
}
}
//1 parameter missing
private fun createDataSet(): LineDataSet? {
var mDataSet = LineDataSet(null, "Data vals")
//mDataSet.setDrawCubic
mDataSet.cubicIntensity = 0.2f
mDataSet.axisDependency = YAxis.AxisDependency.LEFT
mDataSet.setColor(ColorTemplate.getHoloBlue())
mDataSet.setCircleColor(ColorTemplate.getHoloBlue())
mDataSet.lineWidth = 2f
mDataSet.circleSize = 4f
mDataSet.fillAlpha = 65
mDataSet.fillColor = ColorTemplate.getHoloBlue()
mDataSet.highLightColor = Color.rgb(244, 117, 177)
mDataSet.valueTextColor = Color.WHITE
mDataSet.valueTextSize = 10f
return mDataSet
}
private fun ReceiveData() {
val buffer = ByteArray(1024) // buffer store for the stream
var bytes: Int // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
Log.d(TAG, "Inside ReceiveData()")
while (true) { // Read from the InputStream
if(mBluetoothSocket != null)
{
try {
bytes = mBluetoothSocket!!.inputStream.read(buffer)
val incomingMessage = String(buffer, 0, bytes)
Log.d(TAG, "InputStream: $incomingMessage")
yVal = incomingMessage.toInt()
mAddEntry()
} catch (e: IOException) {
Log.e(TAG, "write: Error reading Input Stream. " + e.message)
break
}
}
}
}
}
Here is the logcat log logged in verbose mode.
App starts in the Main Activity(code not attached here), looks for paired devices then using
val intent = Intent(this, ConnectionActivity::class.java)
intent.putExtra(EXTRA_ADDRESS, address)
startActivity(intent) code, control reaches Connection Activity (code attached above).
App is successfully receiving data from server; verified by observing logcat. In ReceiveData(), I am trying to pass the received data to mAddEntry() which is responsible for drawing the graph. But the problem is, data is plotted only after I terminate the socket, so after Line no 112 in the attached logcat log, all the data is plotted at once (no data loss). I want to plot data in real time, and this is the problem I am facing.
Note: graph plotting has been independently verified by passing dummy data in real-time inside onCreate() in ConnectionActivity class using the timer.scheduleAtFixedRate. This is working as expected.
by calling the reciveData() inside the receive_button onClickListner you are executing while loop that will run until Bluetooth socket is connected and it is runing on the MainThread (UIThread) and it will block the UI thread and doesn't let any other ui updateto happen you should execute the reciveData() function on the background and when you want the plot the data in chart you should pass the data in ui thread or call the mAddEntry() function in ui thread
When picking files from the dropbox (V2), I am trying to get the shared link url using this below code.
internal inner class GetDropboxSharableURLTask(file_path: String, pathLower: String) : AsyncTask<String, Void, Void>() {
private var pDialog: ProgressDialog? = null
private var dropbox_url: String? = file_path
private var db_pathLower: String? = pathLower
override fun onPreExecute() {
super.onPreExecute()
pDialog = ProgressDialog(context)
pDialog!!.setCancelable(false)
pDialog!!.setMessage(context?.resources?.getString(R.string.please_wait))
pDialog!!.show()
}
override fun doInBackground(vararg urls: String): Void? {
try {
val shareLink = DropboxClientFactory.getClient().sharing().getSharedLinkMetadata(db_pathLower)
dropbox_url = getShareURL(shareLink.url)!!.replaceFirst("https://www".toRegex(), "https://dl")
return null
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
fun getShareURL(strURL: String?): String? {
var conn: URLConnection? = null
var redirectedUrl: String? = null
try {
val inputURL = URL(strURL)
conn = inputURL.openConnection()
conn!!.connect()
val `is` = conn.getInputStream()
println("Redirected URL: " + conn.url)
redirectedUrl = conn.url.toString()
`is`.close()
} catch (e: MalformedURLException) {
Log.d("", "Please input a valid URL")
} catch (ioe: IOException) {
Log.d("", "Can not connect to the URL")
}
return redirectedUrl
}
override fun onPostExecute(feed: Void?) {
// TODO: check this.exception
// TODO: do something with the feed
pDialog!!.hide()
Log.d("url", dropbox_url)
val intent = Intent()
intent.putExtra(Constant.KEY_DROPBOX_PICKER, dropbox_url)
context?.setResult(Activity.RESULT_OK, intent)
context?.finish()
}
}
I am getting the error as below -
com.dropbox.core.v2.sharing.SharedLinkErrorException: Exception in
2/sharing/get_shared_link_metadata: SHARED_LINK_NOT_FOUND
This above error is thrown when calling getSharedLinkMetadata(db_pathLower)
Though, earlier, I was using the similar code in dropbox V1, but as soon as I switched to newer version of the dropbox API, i.e, dropbox V2, I started getting this error when trying to get the actual sharing url of dropbox, (the one which must contain the file extension as well).
The expected url should be like this - https://dl.dropbox.com/s/hpe1gjfb4aqsv3e/property_1523965639_img.jpeg?dl=0
but, what I am getting is something like https://dl.dropboxusercontent.com/apitl/1/AAD62_lYN5n_cYyRQWhg_rmXGJPFzqF5m8OJpNt_SIIxG7bVvmO6X5d1pKg7uulM1vEBWx_X9PZ9i3vFy-jb3eBC-M_q3YCWRmPrdAwpQ7kqSFGCIPrZaHNC44YRjwXGXYTbnqMO1hPhKb-G5matDzTABUQOssB-LIN4qWoJmPnuhNgzpL9FO4ibet4uBPoef_SLZLjupsOV9PKYUhtPxY_NY7HjymZSHsQh67m4HoBN4YgEAPot0KMAsV1eE3WCjK0XbD1YfGqdsVI9H40KUQ_9R-nmAouoqdbA37G5CXjQKYPC8cENTvN2pjHKwCnHvgI just because of that error.
Please, let me the know the thing, which I need to modify in order to get the dropbox sharing url (along with file extension), and to avoid the above error, as soon as the file is selected / clicked.
Finally, resolved with the help of this, as listSharedLinksBuilder finally worked for me.
It got resolved by modifying the code to -
val sharedLinksResults = DropboxClientFactory.getClient().sharing().listSharedLinksBuilder().withPath(db_pathLower).withDirectOnly(true).start()
var url = ""
if (sharedLinksResults.links.isEmpty()) {
val shareLink = DropboxClientFactory.getClient().sharing().createSharedLinkWithSettings(db_pathLower) //.getClient().sharing().getSharedLinkMetadata(db_pathLower)
url = shareLink.url
} else {
url = sharedLinksResults.links[0].url
}
dropbox_url = getShareURL(url)!!.replaceFirst("https://www".toRegex(), "https://dl")
I am not a fan of polling for information and suspect there is a better way of achieveing what I want.
I am playing an internet radio stream with Android's MediaPlayer. I can find out which tune is playing and by which artist by requesting the 7.html file at the server's address.
My questions are:
Is there a way to receive a notification when a new song begins
to play?
Must I poll the 7.html to find out what is now playing?
If I do have to poll, is there any way in which I can determine
the duration of the current song so I can poll only when a new song
starts?
I guess if I had a low-level stream processing function of my own, I could tell when the song changes because I would receive the meta-data, but I'm not sure how to do that with the Android MediaPlayer class.
Haha, seven years after commenting I finally had to implement this :-D I want a tumbleweed badge for this ;-)
Not to my knowledge
Yes
Not to my knowledge, but polling timers between 30-60 seconds should be fine. At the beginning I wanted to reduce network traffic for users, but this is irrelevant if you are streaming radio at the same time :-D
And here my quick and dirty solution, just in case someone needs it. There are some custom classes in the example, but you ll get the point
import androidx.core.text.HtmlCompat
import de.jahpress.android.main.L
import de.jahpress.android.main.MAX_REQUEST_FOR_SHOUTCAST_TRACK_INFO
import de.jahpress.android.service.Data
import de.jahpress.android.service.radio.model.BaseStation
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
class ShoutCastTrackInfoManager {
private val timeOut = 5L
private val pollingIntervalMs = 60_000L
private var updateTimer: Timer? = null
private var trackInfoThread: Thread? = null
private var invalidTrackInfoCounter = 0
//will ask track info only one time (select station in my use case)
fun updateTrackInfoFor(station: BaseStation, resultCallback: (info: String?) -> Unit) {
L.d("TrackInfo: Get title info for ${station.getStationName()}")
invalidTrackInfoCounter = 0
stopTrackInfoPolling()
requestTrackInfoFromShoutcast(station, resultCallback)
}
//will start track info polling (if station is playing)
fun startTrackInfoPolling(station: BaseStation) {
L.d("TrackInfo: Get title info for ${station.getStationName()}")
stopTrackInfoPolling()
updateTimer = Timer()
updateTimer?.schedule(object : TimerTask() {
override fun run() {
requestTrackInfoFromShoutcast(station, null)
}
}, 0, pollingIntervalMs)
}
fun stopTrackInfoPolling() {
trackInfoThread?.let {
L.d("TrackInfo: Stopping current title update for stream")
it.interrupt()
}
updateTimer?.cancel()
}
private fun requestTrackInfoFromShoutcast(
station: BaseStation,
resultCallback: ((info: String?) -> Unit)?
) {
if (invalidTrackInfoCounter >= MAX_REQUEST_FOR_SHOUTCAST_TRACK_INFO) {
L.d("TrackInfo: $MAX_REQUEST_FOR_SHOUTCAST_TRACK_INFO invalid stream titles. Sto...")
invalidTrackInfoCounter = 0
stopTrackInfoPolling()
Data.currentTitleInfo = null //reset track info
return
}
trackInfoThread = thread {
try {
var trackInfo: String? = null
get7HtmlFromStream(station)?.let {
L.d("TrackInfo: Request track info at $it")
val request = Request.Builder().url(it).build()
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(timeOut, TimeUnit.SECONDS)
.writeTimeout(timeOut, TimeUnit.SECONDS)
.readTimeout(timeOut, TimeUnit.SECONDS)
.build()
val response = okHttpClient.newCall(request).execute()
if (response.isSuccessful) {
val result = response.body?.string()
trackInfo = extractTrackInfoFrom7Html(result)
if (trackInfo != null) {
Data.currentTitleInfo = trackInfo
}
}
response.close()
}
resultCallback?.invoke(trackInfo)
} catch (e: Exception) {
L.e(e)
resultCallback?.invoke(null)
stopTrackInfoPolling()
}
}
}
/**
* Will create Shoutcast 7.html which is located at stream url.
*
* For example: http://66.55.145.43:7473/stream
* 7.html at http://66.55.145.43:7473/7.html
*/
private fun get7HtmlFromStream(station: BaseStation): String? {
val baseStreamUrl = station.getStreamUrl()
L.w("Base url -> $baseStreamUrl")
if (baseStreamUrl == null) return null
val numberSlash = baseStreamUrl.count { c -> c == '/' }
if (numberSlash <= 2) {
return "$baseStreamUrl/7.html"
}
val startOfPath = station.getStreamUrl().indexOf("/", 8)
val streamUrl = station.getStreamUrl().subSequence(0, startOfPath)
return "$streamUrl/7.html"
}
/**
* Will convert webpage to trackinfo. Therefore
* 1. Remove all html-tags
* 2. Get <body> content of webpage
* 3. Extract and return trackinfo
*
* Trackinfo format is always like
* "632,1,1943,2000,439,128,Various Artists - Dance to Dancehall"
* so method will return everything after sixth "," comma character.
*
* Important:
* - Shoutcast might return invalid html
* - Site will return 404 error strings
* - might be empty
*/
private fun extractTrackInfoFrom7Html(html: String?): String? {
L.i("Extract track info from -> $html")
if (html == null) return null
val content = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
val array = content.split(",")
return if (array.size < 7) {
null
} else {
var combinedTrackInfo = ""
for (index in 6 until array.size) {
combinedTrackInfo += "${array[index]} "
}
if (combinedTrackInfo.trim().isEmpty()) {
return null
}
return combinedTrackInfo
}
}
}