Ping website URL before loading it in WebView in Android Kotlin - android

Before loading a website in WebView, I want to check the URL and make sure that it loads. If it does, show the WebView; if not, show another View with a message.
The idea is to check if the website can be loaded and depending on the response show either the "Website cannot be loaded" screen or show the WebView with URL loaded.
I have already checked if the connection is available, so no need to worry about that.
Need to support API 25+.

My solution below is trying to do two things:
Using AsyncTask "ping" a website and then
By passing context from MainActivity, call a function to show WebView (using WeakReference here to achieve that)
class MainActivity : AppCompatActivity() {
private var websiteURL = "https://www.google.com"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ...
webView.webViewClient = WebViewClient()
}
// I called this function after checking that there is Internet connection
private fun connectionResponseFunction(): String {
return if (isConnected) {
// *** is connected
// pass in "this" context from MainActivity
val downloadData = CheckLink(this)
downloadData.execute(websiteURL)
} else {
// *** not connected
}
}
private fun showWebView() {
webView.loadUrl(websiteURL)
}
companion object {
class CheckLink internal constructor(context: MainActivity) : AsyncTask<String, Void, String>() {
// Needed if you want to use webView or anything else from MainActivity
private val activityReference: WeakReference<MainActivity> = WeakReference(context)
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
val activity = activityReference.get()
if (activity == null || activity.isFinishing) return
if (result == "success") {
// URL loaded, show webView
activity.showWebView()
} else {
// URL didn't load
}
}
override fun doInBackground(vararg url: String?): String {
val linkLoaded = loadLink(url[0])
if (!linkLoaded) {
return "failure"
}
return "success"
}
private fun loadLink(urlPath: String?): Boolean {
try {
val url = URL(urlPath)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.setRequestProperty("Connection", "close")
connection.connectTimeout = 3000
connection.connect()
val response = connection.responseCode
// 200 for success
return if (response == 200) {
true
} else {
false
}
} catch (e: Exception) {
// Handle exceptions
when (e) {
is MalformedURLException -> "loadLink: Invalid URL ${e.message}"
is IOException -> "loadLink: IO Exception reading data: ${e.message}"
is SecurityException -> { e.printStackTrace()
"loadLink: Security Exception. Needs permission? ${e.message}"
}
else -> "Unknown error: ${e.message}"
}
}
return false // Error
}
}
}
}
I'm quite new to Android and Kotlin, so I'm open to any suggestions to make it better.
I could not find any recent code that works for API 25+.

Another (shorter) alternative to AsyncTask would it be using Thread:
Thread {
val result = isHostAvailable(BuildConfig.BASE_URL)
runOnUiThread {
if (result) {
// do something ...
}
else showToast("no connection to server")
}
}.start()
and
fun isHostAvailable(urlPath: String): Boolean {
try {
val url = URL(urlPath)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.setRequestProperty("Connection", "close")
connection.connectTimeout = 3000
connection.connect()
return when (connection.responseCode) {
200, 403 -> true
else -> false
}
} catch (e: Exception) {
when (e) {
is MalformedURLException -> "loadLink: Invalid URL ${e.message}"
is IOException -> "loadLink: IO Exception reading data: ${e.message}"
is SecurityException -> {
e.printStackTrace()
"loadLink: Security Exception. Needs permission? ${e.message}"
}
else -> "Unknown error: ${e.message}"
}
}
return false
}

Looks like ping doesn't work on emulators (How to Ping External IP from Java Android)...instead, try this on a real device, it'll work:
val process = Runtime.getRuntime().exec("/system/bin/ping -c 1 $SERVIDOR")
val pingResult = process .waitFor()
return pingResult == 0
By the way, the answer given by #quietbits is pretty obsolete...AsyncTask class was the way to go like 5 years ago...Since Android Studio supports kotlin, you should use coroutines!! the code line difference is huge...check this code labs for more info (https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#0)

Related

Multiple Retrofit calls with Flow

I made app where user can add server (recycler row) to favorites. It only saves the IP and Port. Than, when user open FavoriteFragment Retrofit makes calls for each server
#GET("v0/server/{ip}/{port}")
suspend fun getServer(
#Path("ip") ip: String,
#Path("port") port: Int
): Server
So in repository I mix the sources and make multiple calls:
suspend fun getFavoriteServersToRecyclerView(): Flow<DataState<List<Server>>> = flow {
emit(DataState.Loading)
try {
val getFavoritesServersNotLiveData = favoritesDao.getFavoritesServersNotLiveData()
val list: MutableList<Server> = mutableListOf()
getFavoritesServersNotLiveData.forEach { fav ->
val server = soldatApiService.getServer(fav.ip, fav.port)
list.add(server)
}
emit(DataState.Success(list))
} catch (e: Exception) {
emit(DataState.Error(e))
}
}
and then in ViewModel I create LiveData object
fun getFavoriteServers() {
viewModelScope.launch {
repository.getFavoriteServersToRecyclerView()
.onEach { dataState ->
_favoriteServers.value = dataState
}.launchIn(viewModelScope)
}
}
And everything works fine till the Favorite server is not more available in the Lobby and the Retrofit call failure.
My question is: how to skip the failed call in the loop without crashing whole function.
Emit another flow in catch with emitAll if you wish to continue flow like onResumeNext with RxJava
catch { cause ->
emitAll(flow { emit(DataState.Errorcause)})
}
Ok, I found the solution:
suspend fun getFavoriteServersToRecyclerView(): Flow<DataState<List<Server>>> = flow {
emit(DataState.Loading)
val list: MutableList<Server> = mutableListOf()
try {
val getFavoritesServersNotLiveData = favoritesDao.getFavoritesServersNotLiveData()
val job = CoroutineScope(coroutineContext).launch {
getFavoritesServersNotLiveData.forEach { fav ->
val server = getServer(fav.ip, fav.port)
server.collect { dataState ->
when (dataState) {
is DataState.Loading -> Log.d(TAG, "loading")
is DataState.Error -> Log.d(TAG, dataState.exception.message!!)
is DataState.Success -> {
list.add(dataState.data)
Log.d(TAG, dataState.data.toString())
}
}
}
}
}
job.join()
emit(DataState.Success(list))
} catch (e: Exception) {
emit(DataState.Error(e))
}
}
when using retrofit you can wrap response object with Response<T> (import response from retrofit) so that,
#GET("v0/server/{ip}/{port}")
suspend fun getServer(
#Path("ip") ip: String,
#Path("port") port: Int
): Response<Server>
and then in the Repository you can check if network failed without using try-catch
suspend fun getFavoriteServersToRecyclerView(): Flow<DataState<List<Server>>> = flow {
emit(DataState.Loading)
val getFavoritesServersNotLiveData = favoritesDao.getFavoritesServersNotLiveData()
if(getFavoritesServersNotLiveData.isSuccessful) {
val list: MutableList<Server> = mutableListOf()
getFavoritesServersNotLiveData.body().forEach { fav ->
val server = soldatApiService.getServer(fav.ip, fav.port)
// if the above request fails it wont go to the else block
list.add(server)
}
emit(DataState.Success(list))
} else {
val error = getFavoritesServersNotLiveData.errorBody()!!
//do something with error
}
}

How to check whether it is possible to open url in Kotlin

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
}

how to handle network exception using coroutine?

I try handling exception using coroutine. I wrote code like this, but didn't work. I can't see any log except for using try-catch. I do not want to use try catch at all function, but want to make clean code handling exception. what should I do for this?
viewmodel
private val handler = CoroutineExceptionHandler { _, exception ->
when (exception) {
is UnknownHostException -> {
showLog("login UnknownHostException : " +exception.message)
}
else -> {
}
}
}
fun login(mobile:String){
viewModelScope.launch(handler) {
try{
var login = apiRepository.login(mobile)
_isLogin.value = login
}catch(e:Exception){
}
}
}
repository
override suspend fun login(mobile: String): LoginResultData {
var result =LoginResultData()
withContext(ioDispatcher){
val request = apiServerModel.login(mobile)
val response = request.await()
result = response
}
return result
}
fun login(mobile:String){
viewModelScope.launch(handler) {
val login = apiRepository.login(mobile)
_isLogin.value = login
}
}

Dropbox V2 throwing "SHARED_LINK_NOT_FOUND" when trying to get shared link url

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_rmXGJPFzqF5m8OJp‌​Nt_SIIxG7bVvmO6X5d1pKg7uulM1vEBWx_X9PZ9i3vFy-jb3eBC-M_q3YCWRmPrdAwpQ7kqSFGCIPrZaH‌​NC44YRjwXGXYTbnqMO1hPhKb-G5matDzTABUQOssB-LIN4qWoJmPnuhNgzpL9FO4ibet4uBPoef_SLZLj‌​upsOV9PKYUhtPxY_NY7HjymZSHsQh67m4HoBN4YgEAPot0KMAsV1eE3WCjK0XbD1YfGqdsVI9H40KUQ_9‌​R-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")

How to treat HttpURLConnection when failed to connect to URL

When my database is running, everything is okey. But when is not running my mobile app always crash.
Error message:
Caused by: java.net.ConnectException: Failed to connect to /httpURL.
How to fix problem?
here is my code:
AsyncTaskHandleJson().execute(url)
inner class AsyncTaskHandleJson : AsyncTask<String, String, String>() {
override fun doInBackground(vararg url: String?): String {
var text: String
var connection = URL(url[0]).openConnection() as HttpURLConnection
try {
connection.connect()
text = connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
} finally {
connection.disconnect()
}
return text
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson(result)
}
}
Since there is no catch block in your code, you are not catching any exceptions currently.
If you would like to handle the ConnectException, then you simply have to catch it:
override fun doInBackground(vararg url: String?): String {
var text = ""
var connection: HttpURLConnection? = null
try {
connection = URL(url[0]).openConnection() as HttpURLConnection
connection.connect()
text = connection.inputStream.use {
it.reader().use { reader ->
reader.readText()
}
}
} catch (ce: ConnectException) {
// ConnectionException occurred, do whatever you'd like
ce.printStackTrace()
} catch (e: Exception) {
// some other Exception occurred
e.printStackTrace()
} finally {
connection?.disconnect()
}
return text
}
Check out the Exceptions reference.

Categories

Resources