Calling REST api on android emulator causes app to crash - android

I am relatively new to android studio. I am trying to build a very simple app that fetches the price of cryptocurrencies using the Bittrex exchanges api. However every time I try to get the info from the URL, my app crashes. I am using Kotlin by the way. I'm having trouble solving this because I don't know how to run the emulator in debug mode, just the compiler. Here is my code:
package com.example.sebastian.cryptoapp
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import java.net.URL
import java.net.MalformedURLException
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
fun search(): String {
//read in value
var market = searchBar.getText().toString()
//output text from URL query
val result = URL("https://bittrex.com/api/v1.1/public/getticker?market="
+ market).readText()
return result
}
fun getPrice(): String {
//calling search function
var info = search()
//split the string into a list
var list: List<String> = info.split(":", "}")
//access 7th index of list for last traded price
return list[6]
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
text_output.setText(getPrice())
}
}
}

This line here:
val result = URL("https://bittrex.com/api/v1.1/public/getticker?market=" + market).readText() looks like it is being run on the main thread. This will cause the app to crash with a a NetworkOnMainThreadException.
You can read more on this exception here.
Also, make sure you have the following in your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Check out these answers for more information on how to run this in the background:
How to fix android.os.NetworkOnMainThreadException?
Android - android.os.NetworkOnMainThreadException
Alternatively in Kotlin you could also Anko or Coroutines.

Related

I am learning Android studio and doing a rectangle calculator, my kotlin has 19 errors and cannot figure it out

I am learning Android studio and doing a rectangle calculator. My Kotlin has 19 errors, and I cannot figure it out. I keep getting unresolved errors for btn, functions that cannot be called, and expecting an element. I am trying to do a calculator that takes height and width and then calculates area and perimeter. Just need guidance on what I am doing wrong and not looking for someone to give me new code.
MainActivity.kt
package com.example.calculator
import android.annotation.SuppressLint
import android.icu.text.DecimalFormat
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.*
class MainActivity : AppCompatActivity() {
#SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_calculate.setOnClickListener {
calculate()
btn_calculate.onEditorAction(EditorInfo.IME_ACTION_DONE)
}
btn_reset.setOnClickListener {
reset()
}
}
private fun calculate() {
val formatter = DecimalFormat("#.##")
val editNum1 = (EditText) editNum1.text.toString()
val editNum2 = (EditText) editNum2.text.toString()
val Area = DecimalFormat(editNum1.toDouble() * editNum2.toDouble())
val Perimeter = DecimalFormat(2* ( (editNum1.toDouble()) + (editNum2.toDouble()))
}}
If you want to access the views from your XML file directly in your activity you should use Kotlin Android Extension. But this plugin is deprecated. You should migrate to View Binding.
https://developer.android.com/topic/libraries/view-binding/migration

How can I check to see if JSON data is null without an infinite loop?

I have a viewmodel and data classes that fetch the NASA api for photos of Mars. The user should be displayed images from a random date queried. I always need an image url (imgSrc in Photo class) returned. If no url (imgSrc) is found, refresh data until one is found and display it. This logic would need to return an imgSrc following launch of the application as well as after swiperefreshlayout if the user chooses to swipe to refresh. I have been stuck on this for a week with no resolve. What is the best way to handle this? Even if I have to refactor my code I would like to be pointed in the right direction.
Here is the actual project on github.
JSON that I want to fetch
JSON returning no imgSrc
viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dev20.themarsroll.models.MarsPhotos
import com.dev20.themarsroll.models.Photo
import com.dev20.themarsroll.repository.MarsPhotoRepository
import com.dev20.themarsroll.util.Resource
import kotlinx.coroutines.launch
import retrofit2.Response
class MarsPhotoViewModel(
private val marsPhotoRepository: MarsPhotoRepository
): ViewModel() {
val marsPhotos: MutableLiveData<Resource<MarsPhotos>> = MutableLiveData()
init {
getRandomPhotos()
}
fun getCuriosityPhotos(solQuery: Int, roverQuery: Int, camera: String) = viewModelScope.launch {
marsPhotos.postValue(Resource.Loading())
val response = marsPhotoRepository.getCuriosityPhotos(solQuery, roverQuery, camera)
marsPhotos.postValue(handlePhotosResponse(response))
}
private fun handlePhotosResponse(response: Response<MarsPhotos> ) : Resource<MarsPhotos> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}
return Resource.Error(response.message())
}
fun getRandomPhotos() {
getCuriosityPhotos((1..2878).random(), 5, "NAVCAM")
}
fun savePhoto(photo: Photo) = viewModelScope.launch {
marsPhotoRepository.upsert(photo)
}
fun getSavedPhotos() = marsPhotoRepository.getSavedPhotos()
fun deletePhoto(photo: Photo) = viewModelScope.launch {
marsPhotoRepository.deletePhoto(photo)
}
}
CuriosityFragment
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.View
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.dev20.themarsroll.R
import com.dev20.themarsroll.adapters.MarsPhotoAdapter
import com.dev20.themarsroll.util.Resource
import com.dev20.ui.MarsActivity
import com.dev20.ui.MarsPhotoViewModel
import kotlinx.android.synthetic.main.fragment_curiosity.*
class CuriosityFragment : Fragment(R.layout.fragment_curiosity) {
lateinit var viewModel: MarsPhotoViewModel
lateinit var marsPhotoAdapter: MarsPhotoAdapter
val TAG = "CuriosityFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as MarsActivity).viewModel
setupRecyclerView()
swipeLayout.setOnRefreshListener {
viewModel.getRandomPhotos()
swipeLayout.isRefreshing = false
}
marsPhotoAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("photo", it)
}
findNavController().navigate(
R.id.action_curiosityFragment_to_cameraFragment,
bundle
)
}
viewModel.marsPhotos.observe(viewLifecycleOwner, { response ->
when(response) {
is Resource.Success -> {
hideProgressBar()
response.data?.let { curiosityResponse ->
marsPhotoAdapter.differ.submitList(curiosityResponse.photos)
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Log.e(TAG, "An Error occurred: $message")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
}
private fun hideProgressBar() {
curiosityPaginationProgressBar.visibility = View.INVISIBLE
}
private fun showProgressBar() {
curiosityPaginationProgressBar.visibility = View.VISIBLE
}
private fun setupRecyclerView() {
marsPhotoAdapter = MarsPhotoAdapter()
rvCuriosityPhotos.apply {
adapter = marsPhotoAdapter
layoutManager = LinearLayoutManager(activity)
}
}
}
MarsPhoto data class
data class MarsPhotos(
val photos: MutableList<Photo>,
val camera: MutableList<Camera>
)
Photo data class
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.google.gson.annotations.SerializedName
import java.io.Serializable
#Entity(
tableName = "photos"
)
#TypeConverters
data class Photo(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
#SerializedName("earth_date")
val earthDate: String,
#SerializedName("img_src")
val imgSrc: String,
val sol: Int,
#SerializedName("rover_id")
val rover: Int,
) : Serializable
There are many potential solutions here that I can think of. However, given the app needs to have predictable and reasonable user experience, herein I'm scoping out the issues first.
Since a random resource is being requested each time, there's always a chance of it being null. Hence, multiple round-trips cannot be done away with (but can be reduced).
Multiple HTTP round-trips, that too with the unpredictability of returning null several times, can be really frustrating user experience.
Below are the potential ways (in increasing order of complexity) this can be dealt with.
The simplest solution is to implement logic on the repository level, wherein the function getCuriosityPhotos is responsible to request the api resource indefinitely till it responds with a not null data. This will solve the core issue that the user will eventually be shown something (but it might take a hell lot of time).
(PS- You'll also need to delegate the random number generation as a potential service available to the repository.)
To reduce the number of requests and hence wait-time for the user, you can save to the in-app database, the request params as well as the response. Thus, your database can act as a single source of truth. Hence, before making a request, you can query the database to check if the app had previously requested the same params earlier. If it did not, dispatch the request else if it did, then there's no need to request again and you can use the previous result. If it was null, regenerate another random number and try again. If it was not null, serve the data from the database. (This is a good enough solution and as more and more requests & responses are saved, user wait time would continually reduce)
(Note: In case the endpoints do not respond with static data and the data keeps changing, prefer using an in-memory database than a persistent database such as SQLite)
The app can run a background service that continually (by iterating over all possible combinations of the request params) requests and saves the data into the database. When the user requests random data, the app should display a random set of data from within the database. If the database is empty/does not meet a threshold of having at least n number of rows in the database, the app can perhaps show an initialization setup UI.
Pro-tip: Ideally (and in case you are building a product/service), mobile apps are meant to be very very predictable and have to be mindful of a user's time. Hence, the very task of requesting data from such resources should be a task of a backend server and database which operate some sort of service to fetch and store data and in-turn the app would request this server to fetch data amongst this subset which does not have any null values.
I've answered this question from a perspective of solving the problem with varying granularity. In case you need help/advice on the technical implementation part, let me know, I'll be happy to help!

Android MVVM multiple API Call

I'm on my way to make an MVVM example project without the complexity of injection dependency library and RX ( because I think it's better to understand how it works fundamentally for people without all this very efficient stuff ) but its harder to make :)
I'm in trouble, I use the CatApi here: https://thecatapi.com/
I'm trying to do a spinner that contains breeds name and also a picture of a cat to the left ( for each breed ) but in this API you can only get this result in two calls ( one for breeds, one for images for a breed ), I don't push the research on the API far because even if the API can solve my problem, I want to face the problem because it can happen later in my life :)
So there is my probleme i've made the following code :
BreedEntity :
package com.example.mvvm_kitty.data.local.entities
//Entity was used to be stored into a local DB so no use here
data class BreedEntity (
val adaptability: Int,
val affection_level: Int,
val description: String,
val id: String,
var name: String,
val life_span: String,
val origin: String,
var iconImage : BreedImageEntity?,
var images: List<BreedImageEntity>?
){
}
the call into the BreedActivity :
private fun subscribeToModel(breedsViewModel: BreedsViewModel) {
//Todo: Gerer les erreurs reseau
breedsViewModel.getBreeds().observe(this, Observer {
breedEntities ->
mBinding.catSelected = breedEntities[0]
breedSpinnerAdapter = BreedsSpinnerAdapter(this, breedEntities)
mBinding.breedSelector.adapter = breedSpinnerAdapter
breedEntities.forEach {breedEntity ->
breedsViewModel.getBreedImages(breedEntities.indexOf(breedEntity)).observe(this, Observer {
breedEntity.iconImage = it[0]
})
}
})
}
yeah I think made a foreach it's very dirty ( and also it doesn't work because don't run on the same time so when I set the images in the observer the "it" value is on the last item
there is my BreedsViewModel :
package com.example.mvvm_kitty.viewmodels
import android.app.Application
import android.util.Log
import android.view.animation.Transformation
import androidx.lifecycle.*
import com.example.mvvm_kitty.BasicApp
import com.example.mvvm_kitty.data.local.entities.BreedEntity
import com.example.mvvm_kitty.data.local.entities.BreedImageEntity
import com.example.mvvm_kitty.data.repositories.CatRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
class BreedsViewModel(application: Application, private val catRepository: CatRepository) : AndroidViewModel(application) {
private val mObservableBreeds: LiveData<List<BreedEntity>> = catRepository.getBreeds()
/**
* Expose the product to allow the UI to observe it
*/
fun getBreeds(): LiveData<List<BreedEntity>> {
return mObservableBreeds
}
fun getBreedImages(index : Int): LiveData<List<BreedImageEntity>> {
val breed = mObservableBreeds.value?.get(index)
return catRepository.getBreedImages(breed!!.id)
}
/**
* Factory is used to inject dynamically all dependency to the viewModel like reposiroty, or id
* or whatever
*/
class Factory(private val mApplication: Application) :
ViewModelProvider.NewInstanceFactory() {
private val mRepository: CatRepository = (mApplication as BasicApp).getCatRepository()
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return BreedsViewModel(mApplication, mRepository) as T
}
}
}
and to finish the CatRepository method to get the images :
private fun getBreedImagesFromApi(id: String) : LiveData<List<BreedImageEntity>>{
mObservableBreedImages.addSource(catService.getAllImages(id, 10)){
mObservableBreedImages.postValue(it.resource?.map { breedDto ->
breedDto.toEntity()})
}
return mObservableBreedImages
}
My problem is the following how can I get my images for each item in a clean way ( because I think my code is good but the foreach observer part is very dirty )
If someone can help me it would be very nice :D, Thanks in advance for your time.
Seems like instead of fetching the data separately, you should be fetching them at the same time and combining the result into one response.
Generally speaking:
Remove getBreedImagesFromApi.
Update your getBreedsFromApi (I
assume that exists and you're using coroutines) to fetch both pieces
of data in the one call you already have for getting breeds. You can
use async and await() for this to fire off two requests to the two different endpoints, have them run concurrently, and wait for both requests to finish.
Remove the "foreach" because the images will now exist by the time you get the list of breeds.
Hope that helps!
I guess the view should receive a completed object data from your view-model,
which mean you must do all this logic in view model.so
hitting both APIs & observe them in view-model.
create one more Live Data responsible for passing data to your View(Activity/Fragment)
update the ViewLiveData from both APIs observers.
and to do this you will need Transformation or MediatorLiveData in your view-model.
also you can use Rxjava, check fetch every item on the list

Unresolved referance "firebase" when trying to call TIMESTAMP, while all other services work

I am trying to create a time stamp using the firebase server value.
In the documentation in says to use
firebase.database.ServerValue.TIMESTAMP
*edit: before I didn't realize I was looking at the JS documentation. I've relinked it to the Android ones though they don't seem to specify a format there. I'm still looking.
But it return unresolved referance "firebase"
I've looked at other similar question, and they offered other formats like Firebase.ServerValue.TIMESTAMP and i've tried them but they don't work either and I think they're outdated.
My other firebase services are working just fine (Authorization, Database & Storage) so I can't understand why am I getting this error.
I am trying o achive that to create a simple timestamp the I will later, after pulled from the server, would convert to a nicer format with PrettyTime.
This is the part of the code the is giving me the error:
class NewQuestionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_question)
val questionTitle : EditText = findViewById(R.id.new_question_title)
val questionDetails = findViewById<EditText>(R.id.new_question_details)
val questionTags = findViewById<EditText>(R.id.new_question_tags)
val questionButton = findViewById<Button>(R.id.new_question_btn)
questionButton.setOnClickListener {
postQuestion(questionTitle.text.toString(), questionDetails.text.toString(), questionTags.text.toString(), firebase.database.ServerValue.TIMESTAMP)
}
}
private fun postQuestion(title : String, details : String, tags : String, timestamp : String) {
val uid = FirebaseAuth.getInstance().uid
val ref = FirebaseDatabase.getInstance().getReference("/questions").push()
val newQuestion = Question(title, details, tags, timestamp)
ref.setValue(newQuestion)
.addOnSuccessListener {
Log.d("postQuestionActivity", "Saved question to Firebase Database")
}.addOnFailureListener {
Log.d("postQuestionActivity", "Failed to save question to database")
}
}
}
It is very odd a class start with lower case. I guess the most suitable approach is to allow the IDE to make the import for you, try with
ServerValue.TIMESTAMP

Make a webview search bar that searches Google and URL's

I'm 16 and trying to learn Kotlin, I'm very new. I want to create a search bar which searches both Google and can open any URL you type in. I was trying to use an if else statement, for example:
if the first three letters were "www." then use the string url which is equal to "https://",
else use the string start_url which is equal to "google.com/search?q=";.
I just do not know how to do that and I have tried looking for help across the internet I just couldn't.
The URLUtil.isValidUrl(url) will not work because it still only loads Google Search
I am open to any comments to try and help me further learn and improve my code, even if it is not related to my question. Point out any errors or things that could be improved in my code, I know it's not perfect, thank you!
package com.example.corie.quicklinks.mainpages
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.asynclayoutinflater.R.id.text
import android.webkit.WebChromeClient
import android.webkit.WebViewClient
import com.example.corie.quicklinks.R
import com.example.corie.quicklinks.R.string.start_url
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//------------------WEBVIEW-----------------//
webViewOne.webChromeClient = WebChromeClient()
webViewOne.isVerticalScrollBarEnabled = false
webViewOne.run{
webViewOne.loadUrl("https://" + getString(start_url))
}
goBtn.setOnClickListener{
webViewOne.loadUrl("https://www.google.com/search?q=" + editText.text.toString())
}
backBtn.setOnClickListener {
if (webViewOne.canGoBack())
webViewOne.goBack()
}
nextBtn.setOnClickListener {
if (webViewOne.canGoForward())
webViewOne.goForward()
}
//------------------WEBVIEW-----------------//
}
}
Simple with built-in regex pattern:
import android.util.Patterns
val isAddress = Patterns.WEB_URL.matcher(address).matches()
if (isAddress) {
this#WebpageFragment.mBinding.webpageWebView
.loadUrl(address)
} else {
this#WebpageFragment.mBinding.webpageWebView.loadUrl(
"https://www.google.com/search?q=$address"
)
}
Explain: First you need to check if the string is an URL. If it's an URL, you'll use the webview to load that URL; Otherwise, you will need to use that value as google search query.

Categories

Resources