So i have built my Ktor backend and im watching a tutorial from philip lackner Tutorial
He starts of with making a withTestApplication call within his testfunction... The thing is that this has been deprecated and it would be nice to use the newer version of it which i understand is
#Test
fun `Create user, no body attached, responds with BadRequest`() = testApplication{
application {
install(Routing) {
createUserRoute(userRepository)
}
}
}
And so i use this code but i have no clue how to get the scope so i can use handleRequest?
please can somebody help me with this?
This is the code i wanted to test
package com.PapperSeller.routes
import com.PapperSeller.controller.user.UserRepository
import com.PapperSeller.data.models.User
import com.PapperSeller.data.requests.CreateAccountRequest
import com.PapperSeller.data.responses.BasicApiResponse
import com.PapperSeller.util.ApiResponseMessages
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.inject
fun Route.createUserRoute(repository: UserRepository){
route("/api/user/create"){
post {
kotlin.runCatching { call.receive<CreateAccountRequest>()}.onSuccess {request ->
val userExist = repository.getUserByEmail(request.email) != null
if(userExist){
call.respond(BasicApiResponse(ApiResponseMessages.USER_ALREADY_EXIST, false))
return#post
}else{
if(request.email.isBlank() || request.username.isBlank() || request.password.isBlank()){
call.respond(BasicApiResponse(ApiResponseMessages.FIELDS_EMPTY, false))
return#post
}else{
repository.createUser(User(email = request.email, username = request.username, password = request.password))
call.respond(BasicApiResponse("", true))
return#post
}
}
}.onFailure {
call.respond(BasicApiResponse(ApiResponseMessages.NO_USER_REGISTERED, false))
return#post
}
}
}
}
Here is an example of how you can cover the Route.createUserRoute with tests:
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import kotlinx.serialization.Serializable
import kotlin.test.Test
import kotlin.test.assertEquals
class KtorTest {
#Test
fun userNotRegistered() = testApplication {
application {
module(MemoryUserRepository())
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
assertEquals(
BasicApiResponse(ApiResponseMessages.NO_USER_REGISTERED, false),
client.post("/api/user/create").body()
)
}
#Test
fun userAlreadyExist() = testApplication {
application {
module(MemoryUserRepository(mapOf("john#doe" to User("", "", ""))))
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
client.post("/api/user/create") {
setBody(CreateAccountRequest("john#doe", "", ""))
contentType(ContentType.Application.Json)
}.body<BasicApiResponse>().let { response ->
assertEquals(BasicApiResponse(ApiResponseMessages.USER_ALREADY_EXIST, false), response)
}
}
#Test
fun emptyRequestFields() = testApplication {
application {
module(MemoryUserRepository())
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
client.post("/api/user/create") {
setBody(CreateAccountRequest("", "", ""))
contentType(ContentType.Application.Json)
}.body<BasicApiResponse>().let { response ->
assertEquals(BasicApiResponse(ApiResponseMessages.FIELDS_EMPTY, false), response)
}
}
#Test
fun successfulRegistration() = testApplication {
application {
module(MemoryUserRepository())
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
client.post("/api/user/create") {
setBody(CreateAccountRequest("john#doe", "john", "123"))
contentType(ContentType.Application.Json)
}.body<BasicApiResponse>().let { response ->
assertEquals(BasicApiResponse("", true), response)
}
}
}
fun Application.module(repository: UserRepository) {
install(io.ktor.server.plugins.contentnegotiation.ContentNegotiation) {
json()
}
routing {
createUserRoute(repository)
}
}
fun Route.createUserRoute(repository: UserRepository) {
route("/api/user/create") {
post {
kotlin.runCatching { call.receive<CreateAccountRequest>() }.onSuccess { request ->
val userExist = repository.getUserByEmail(request.email) != null
if (userExist) {
call.respond(BasicApiResponse(ApiResponseMessages.USER_ALREADY_EXIST, false))
return#post
} else {
if (request.email.isBlank() || request.username.isBlank() || request.password.isBlank()) {
call.respond(BasicApiResponse(ApiResponseMessages.FIELDS_EMPTY, false))
return#post
} else {
repository.createUser(
User(
email = request.email,
username = request.username,
password = request.password
)
)
call.respond(BasicApiResponse("", true))
return#post
}
}
}.onFailure {
call.respond(BasicApiResponse(ApiResponseMessages.NO_USER_REGISTERED, false))
return#post
}
}
}
}
class ApiResponseMessages {
companion object {
val USER_ALREADY_EXIST = "User does already exist"
val FIELDS_EMPTY = "Something missing"
val NO_USER_REGISTERED = "User isn't registered"
}
}
interface UserRepository {
fun getUserByEmail(email: String): User?
fun createUser(user: User)
}
class MemoryUserRepository(initialUsers: Map<String, User> = mapOf()): UserRepository {
private val users = initialUsers.toMutableMap()
override fun getUserByEmail(email: String): User? {
return users[email]
}
override fun createUser(user: User) {
users[user.email] = user
}
}
#Serializable
data class CreateAccountRequest(val email: String, val username: String, val password: String)
data class User(val email: String, val username: String, val password: String)
#Serializable
data class BasicApiResponse(val message: String, val success: Boolean)
Related
I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}
Does anyone have an idea how to convert these codes to ktor 2.0.
https://gist.github.com/togisoft/d1113a83eeb1d6b52031f77fe780ce48
If someone needs to see a complete sample of Aleksei's answer, I created a sample repository.
I did make some slight tweaks to the other answer as the implementation error was missing the correct messaging from the original gist.
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseToken
import io.ktor.http.auth.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class FirebaseAuthProvider(config: FirebaseConfig): AuthenticationProvider(config) {
val authHeader: (ApplicationCall) -> HttpAuthHeader? = config.authHeader
private val authFunction = config.firebaseAuthenticationFunction
override suspend fun onAuthenticate(context: AuthenticationContext) {
val token = authHeader(context.call)
if (token == null) {
context.challenge(FirebaseJWTAuthKey, AuthenticationFailedCause.InvalidCredentials) { challengeFunc, call ->
challengeFunc.complete()
call.respond(UnauthorizedResponse(HttpAuthHeader.bearerAuthChallenge(realm = FIREBASE_AUTH)))
}
return
}
try {
val principal = verifyFirebaseIdToken(context.call, token, authFunction)
if (principal != null) {
context.principal(principal)
}
} catch (cause: Throwable) {
val message = cause.message ?: cause.javaClass.simpleName
context.error(FirebaseJWTAuthKey, AuthenticationFailedCause.Error(message))
}
}
}
class FirebaseConfig(name: String?) : AuthenticationProvider.Config(name) {
internal var authHeader: (ApplicationCall) -> HttpAuthHeader? =
{ call -> call.request.parseAuthorizationHeaderOrNull() }
var firebaseAuthenticationFunction: AuthenticationFunction<FirebaseToken> = {
throw NotImplementedError(FirebaseImplementationError)
}
fun validate(validate: suspend ApplicationCall.(FirebaseToken) -> User?) {
firebaseAuthenticationFunction = validate
}
}
public fun AuthenticationConfig.firebase(name: String? = FIREBASE_AUTH, configure: FirebaseConfig.() -> Unit) {
val provider = FirebaseAuthProvider(FirebaseConfig(name).apply(configure))
register(provider)
}
suspend fun verifyFirebaseIdToken(
call: ApplicationCall,
authHeader: HttpAuthHeader,
tokenData: suspend ApplicationCall.(FirebaseToken) -> Principal?
): Principal? {
val token: FirebaseToken = try {
if (authHeader.authScheme == "Bearer" && authHeader is HttpAuthHeader.Single) {
withContext(Dispatchers.IO) {
FirebaseAuth.getInstance().verifyIdToken(authHeader.blob)
}
} else {
null
}
} catch (ex: Exception) {
ex.printStackTrace()
return null
} ?: return null
return tokenData(call, token)
}
private fun HttpAuthHeader.Companion.bearerAuthChallenge(realm: String): HttpAuthHeader {
return HttpAuthHeader.Parameterized("Bearer", mapOf(HttpAuthHeader.Parameters.Realm to realm))
}
private fun ApplicationRequest.parseAuthorizationHeaderOrNull() = try {
parseAuthorizationHeader()
} catch (ex: IllegalArgumentException) {
println("failed to parse token")
null
}
const val FIREBASE_AUTH = "FIREBASE_AUTH"
private const val FirebaseJWTAuthKey: String = "FirebaseAuth"
private const val FirebaseImplementationError =
"Firebase auth validate function is not specified, use firebase { validate { ... } }to fix"
Then to actually use in your project created an extension function on Application. Be sure that the Firebase Admin SDK has been initialized with credentials before installing the Firebase authentication plugin on Ktor.
fun Application.configureFirebaseAuth() {
FirebaseAdmin.init()
install(Authentication) {
firebase {
validate {
// TODO look up user profile to fill in any additional information on top of firebase user profile
User(it.uid, it.name)
}
}
}
}
Finally wrap a route with the authentication function:
authenticate(FIREBASE_AUTH) {
get("/authenticated") {
val user: User = call.principal() ?: return#get call.respond(HttpStatusCode.Unauthorized)
call.respond("User is authenticated: $user")
}
}
The converted to Ktor 2.0.* code is the following:
import io.ktor.http.auth.*
import io.ktor.serialization.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseToken
class FirebaseAuthProvider(config: FirebaseConfig): AuthenticationProvider(config) {
val authHeader: (ApplicationCall) -> HttpAuthHeader? = config.authHeader
private val authFunction = config.firebaseAuthenticationFunction
override suspend fun onAuthenticate(context: AuthenticationContext) {
val token = authHeader(context.call)
if (token == null) {
context.challenge(FirebaseJWTAuthKey, AuthenticationFailedCause.InvalidCredentials) { challengeFunc, call ->
challengeFunc.complete()
call.respond(UnauthorizedResponse(HttpAuthHeader.bearerAuthChallenge(realm = "firebaseAuth")))
}
return
}
try {
val principal = verifyFirebaseIdToken(context.call, token, authFunction)
if (principal != null) {
context.principal(principal)
}
} catch (cause: Throwable) {
val message = cause.message ?: cause.javaClass.simpleName
context.error(FirebaseJWTAuthKey, AuthenticationFailedCause.Error(message))
}
}
}
class FirebaseConfig(name: String?) : AuthenticationProvider.Config(name) {
internal var authHeader: (ApplicationCall) -> HttpAuthHeader? =
{ call -> call.request.parseAuthorizationHeaderOrNull() }
var firebaseAuthenticationFunction: AuthenticationFunction<FirebaseToken> = {
throw NotImplementedError(FirebaseImplementationError)
}
fun validate(validate: suspend ApplicationCall.(FirebaseToken) -> User?) {
firebaseAuthenticationFunction = validate
}
}
public fun AuthenticationConfig.firebase(name: String? = "firebaseAuth", configure: FirebaseConfig.() -> Unit) {
val provider = FirebaseAuthProvider(FirebaseConfig(name).apply(configure))
register(provider)
}
suspend fun verifyFirebaseIdToken(
call: ApplicationCall,
authHeader: HttpAuthHeader,
tokenData: suspend ApplicationCall.(FirebaseToken) -> Principal?
): Principal? {
val token: FirebaseToken = try {
if (authHeader.authScheme == "Bearer" && authHeader is HttpAuthHeader.Single) {
withContext(Dispatchers.IO) {
FirebaseAuth.getInstance().verifyIdToken(authHeader.blob)
}
} else {
null
}
} catch (ex: Exception) {
ex.printStackTrace()
return null
} ?: return null
return tokenData(call, token)
}
private fun HttpAuthHeader.Companion.bearerAuthChallenge(realm: String): HttpAuthHeader {
return HttpAuthHeader.Parameterized("Bearer", mapOf(HttpAuthHeader.Parameters.Realm to realm))
}
private fun ApplicationRequest.parseAuthorizationHeaderOrNull() = try {
parseAuthorizationHeader()
} catch (ex: IllegalArgumentException) {
println("failed to parse token")
null
}
private const val FirebaseJWTAuthKey: String = "FirebaseAuth"
private const val FirebaseImplementationError =
"Firebase auth validate function is not specified, use firebase { { ... } }to fix"
I have the code to monitor if internet is available. It returns a LiveData and it is observed in the MainActivity . The code is given below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding=DataBindingUtil.setContentView(this,R.layout.activity_main)
NetworkStatusHelper(this#MainActivity).observe(this, Observer {
when(it){
NetworkStatus.Available-> Snackbar.make(binding.root, "Back online", Snackbar.LENGTH_LONG).show()
NetworkStatus.Unavailable-> Snackbar.make(binding.root, "No Internet connection", Snackbar.LENGTH_LONG).show()
}
})
}
NetworkHelper
package com.todo.utils.networkhelper
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.util.Log
import androidx.lifecycle.LiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
class NetworkStatusHelper(private val context: Context): LiveData<NetworkStatus>() {
var connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback
val validNetworkConnections: ArrayList<Network> = ArrayList()
fun getConnectivityCallbacks() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val networkCapability =
connectivityManager.getNetworkCapabilities(network)
val hasNetworkConnection =
networkCapability?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
if (hasNetworkConnection) {
determineInternetAccess(network)
}
}
override fun onLost(network: Network) {
super.onLost(network)
validNetworkConnections.remove(network)
announceNetworkStatus()
}
// override fun onCapabilitiesChanged(
// network: Network,
// networkCapabilities: NetworkCapabilities
// ) {
// super.onCapabilitiesChanged(network, networkCapabilities)
//
// Log.d("validNetworkConnection","onCapabilitiesChanged size "+validNetworkConnections.size)
//
//
// if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
// determineInternetAccess(network)
// } else {
// validNetworkConnections.remove(network)
// }
// announceNetworkStatus()
// }
private fun determineInternetAccess(network: Network) {
CoroutineScope(Dispatchers.IO).launch {
if (InternetAvailability.check()) {
withContext(Dispatchers.Main) {
validNetworkConnections.add(network)
announceNetworkStatus()
}
}
}
}
fun announceNetworkStatus() {
if (validNetworkConnections.isNotEmpty()) {
postValue(NetworkStatus.Available)
} else {
postValue(NetworkStatus.Unavailable)
}
}
}
} else {
TODO("VERSION.SDK_INT < LOLLIPOP")
}
override fun onActive() {
super.onActive()
connectivityManagerCallback = getConnectivityCallbacks()
val networkRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
NetworkRequest
.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
} else {
TODO("VERSION.SDK_INT < LOLLIPOP")
}
connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
}
override fun onInactive() {
super.onInactive()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
}
}
object InternetAvailability {
fun check() : Boolean {
return try {
val socket = Socket()
socket.connect(InetSocketAddress("8.8.8.8",53))
socket.close()
true
} catch ( e: Exception){
e.printStackTrace()
false
}
}
}
}
The problem is here is , the Snackbar is displayed even when the app is opened for the first time .I don't want the Snackbar to be displayed when the app is opened for the first time when network is available. If network is unavailable, then the Snackbar should be displayed even when the app is opened for the first time.
Can someone help to improve the code with correct logic to implement the same.
If your helper class is a Flow, then you can use Flow operators to easily customize its behavior. You should keep the instance of your helper class in a ViewModel so it can maintain its state when there are configuration changes.
Here's a Flow version of your class's functionality. I actually just made it into a function, because I think that's simpler.
I removed the List<Network> but you can add it back in if you think it's necessary. I don't think it makes sense to keep a List that can only ever hold at most one item. A device cannot have multiple simultaneous network connections. If you do need it, it won't work for pre-Lollipop, so you will have to juggle differing functionality and probably do need a class instead of just a function.
I think you can probably remove the checkAvailability() function as it is redundant, but I put it in because you have it.
I added a pre-Lollipop version based on a broadcast receiver, since you seem to want to add support for that.
#get:RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
val Context.networkStatus: Flow<NetworkStatus> get() = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> getNetworkStatusLollipop(this)
else -> getNetworkStatusPreLollipop(this)
}
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
#RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
private fun getNetworkStatusLollipop(context: Context): Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val callback = object : ConnectivityManager.NetworkCallback() {
private var availabilityCheckJob: Job? = null
override fun onUnavailable() {
availabilityCheckJob?.cancel()
trySend(NetworkStatus.Unavailable)
}
override fun onAvailable(network: Network) {
availabilityCheckJob = launch {
send(if(checkAvailability()) NetworkStatus.Available else NetworkStatus.Unavailable)
}
}
override fun onLost(network: Network) {
availabilityCheckJob?.cancel()
trySend(NetworkStatus.Unavailable)
}
}
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
#RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
private fun getNetworkStatusPreLollipop(context: Context): Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val receiver = object: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
launch {
if (connectivityManager.activeNetworkInfo?.isConnectedOrConnecting == true) {
send(if(checkAvailability()) NetworkStatus.Available else NetworkStatus.Unavailable)
} else {
send(NetworkStatus.Unavailable)
}
}
}
}
context.registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
awaitClose { context.unregisterReceiver(receiver) }
}
private suspend fun checkAvailability() : Boolean = withContext(Dispatchers.IO) {
try {
Socket().use {
it.connect(InetSocketAddress("8.8.8.8", 53))
}
true
} catch (e: Exception){
e.printStackTrace()
false
}
}
Then in your ViewModel, you can use Flow operators to easily expose a Flow that skips initial NetworkStatus.Available values:
class MyViewModel(application: Application): AndroidViewModel(application) {
val changedNetworkStatus = application.context.networkStatus
.dropWhile { it == NetworkStatus.Available } // ignore initial available status
.shareIn(viewModelScope, SharingStarted.Eagerly, 1) // or .asLiveData() if preferred
}
I have an insert,update,read method in helperclass,how can I use in the following class?
Im not very used to kotlin but id like to now how can we insert the DB methods and integrate it with the code below
After adding a checkfield method ive been receiving an error when i add it in the onclicklistner
package com.google.samples.apps.topeka.fragment
import android.annotation.TargetApi
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.v4.app.ActivityOptionsCompat
import android.support.v4.app.Fragment
import android.support.v4.util.Pair
import android.support.v4.view.ViewCompat
import android.support.v4.view.animation.FastOutSlowInInterpolator
import android.text.Editable
import android.text.TextWatcher
import android.transition.Transition
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.Toast.LENGTH_LONG
import com.google.samples.apps.topeka.adapter.AvatarAdapter
import com.google.samples.apps.topeka.base.R
import com.google.samples.apps.topeka.helper.ActivityLaunchHelper
import com.google.samples.apps.topeka.helper.ApiLevelHelper
import com.google.samples.apps.topeka.helper.DefaultLogin
import com.google.samples.apps.topeka.helper.TAG
import com.google.samples.apps.topeka.helper.TransitionHelper
import com.google.samples.apps.topeka.helper.isLoggedIn
import com.google.samples.apps.topeka.helper.login
import com.google.samples.apps.topeka.helper.onLayoutChange
import com.google.samples.apps.topeka.helper.onSmartLockResult
import com.google.samples.apps.topeka.model.Avatar
import com.google.samples.apps.topeka.model.Player
import com.google.samples.apps.topeka.persistence.TopekaDatabaseHelper
import com.google.samples.apps.topeka.widget.TextWatcherAdapter
import com.google.samples.apps.topeka.widget.TransitionListenerAdapter
/**
* Enable selection of an [Avatar] and user name.
*/
class SignInFragment : Fragment() {
private var firstNameView: EditText? = null
private var lastInitialView: EditText? = null
private var doneFab: FloatingActionButton? = null
private var avatarGrid: GridView? = null
private val edit by lazy { arguments?.getBoolean(ARG_EDIT, false) ?: false }
private var selectedAvatarView: View? = null
private var player: Player? = null
private var selectedAvatar: Avatar? = null
var a = TopekaDatabaseHelper.getInstance(this.requireContext())
override fun onCreate(savedInstanceState: Bundle?) {
if (savedInstanceState != null) {
val avatarIndex = savedInstanceState.getInt(KEY_SELECTED_AVATAR_INDEX)
if (avatarIndex != GridView.INVALID_POSITION) {
selectedAvatar = Avatar.values()[avatarIndex]
}
}
activity?.run {
if (isLoggedIn()) {
navigateToCategoryActivity()
} else {
login.loginPlayer(this, ::onSuccessfulLogin)
}
}
super.onCreate(savedInstanceState)
}
/**
* Called when logged in successfully.
*/
private fun onSuccessfulLogin(player: Player) {
if (login != DefaultLogin) return
this.player = player
if (edit) {
with(player) {
firstNameView?.setText(player.firstName)
lastInitialView?.run {
setText(player.lastInitial)
requestFocus()
setSelection(length())
}
this#SignInFragment.player = player.also {
if (activity != null)
login.savePlayer(activity!!, this, { selectAvatar(it.avatar!!) })
}
}
} else {
navigateToCategoryActivity()
}
}
private fun navigateToCategoryActivity() {
activity?.run {
ActivityLaunchHelper.launchCategorySelection(this)
supportFinishAfterTransition()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
activity?.onSmartLockResult(
requestCode,
resultCode,
data,
success = {
player = it
initContents()
navigateToCategoryActivity()
},
failure = {
activity?.run {
login.loginPlayer(this, ::onSuccessfulLogin)
}
}
)
super.onActivityResult(requestCode, resultCode, data)
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val contentView = inflater.inflate(R.layout.fragment_sign_in, container, false)
val Button =
contentView.onLayoutChange {
avatarGrid?.apply {
adapter = AvatarAdapter(activity!!)
onItemClickListener = AdapterView.OnItemClickListener { _, view, position, _ ->
selectedAvatarView = view
selectedAvatar = Avatar.values()[position]
// showing the floating action button if input data is valid
showFab()
}
numColumns = calculateSpanCount()
selectedAvatar?.run { selectAvatar(this) }
}
}
return contentView
}
/**
* Calculates spans for avatars dynamically.
* #return The recommended amount of columns.
*/
private fun calculateSpanCount(): Int {
val avatarSize = resources.getDimensionPixelSize(R.dimen.size_fab)
val avatarPadding = resources.getDimensionPixelSize(R.dimen.spacing_double)
return (avatarGrid?.width ?: 0) / (avatarSize + avatarPadding)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(KEY_SELECTED_AVATAR_INDEX, (avatarGrid?.checkedItemPosition ?: 0))
super.onSaveInstanceState(outState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
firstNameView = view.findViewById<EditText>(R.id.first_name)
lastInitialView = view.findViewById<EditText>(R.id.last_initial)
doneFab = view.findViewById<FloatingActionButton>(R.id.done)
avatarGrid = view.findViewById<GridView>(R.id.avatars)
if (edit || (player != null && player!!.valid())) {
initContentViews()
initContents()
}
hideEmptyView()
super.onViewCreated(view, savedInstanceState)
}
private fun hideEmptyView() {
view?.run {
findViewById<View>(R.id.empty).visibility = View.GONE
findViewById<View>(R.id.content).visibility = View.VISIBLE
}
}
private fun initContentViews() {
val textWatcher = object : TextWatcher by TextWatcherAdapter {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// hiding the floating action button if text is empty
if (s.isEmpty()) {
doneFab?.hide()
}
}
// showing the floating action button if avatar is selected and input data is valid
override fun afterTextChanged(s: Editable) {
if (isAvatarSelected() && isInputDataValid()) doneFab?.show()
}
}
firstNameView?.addTextChangedListener(textWatcher)
lastInitialView?.addTextChangedListener(textWatcher)
doneFab?.setOnClickListener {
if (it.id == R.id.done) {
var first = firstNameView?.text?.toString()
var last = lastInitialView?.text?.toString()
//
try {
a.adduser(first,last,"string")
}
catch (e:Exception) {
// handler
}
activity?.run {
val toSave = player?.apply {
// either update the existing player object
firstName = first
lastInitial = last
avatar = selectedAvatar
a.adduser(first,last,avatar.toString())
} ?: Player(first, last, selectedAvatar) /* or create a new one */
login.savePlayer(this, toSave) {
Log.d(TAG, "Saving login info successful.")
}
}
}
removeDoneFab {
performSignInWithTransition(selectedAvatarView
?: avatarGrid?.getChildAt(selectedAvatar!!.ordinal))
}
}
}
private fun removeDoneFab(endAction: () -> Unit) {
ViewCompat.animate(doneFab)
.scaleX(0f)
.scaleY(0f)
.setInterpolator(FastOutSlowInInterpolator())
.withEndAction(endAction)
.start()
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun performSignInWithTransition(v: View? = null) {
if (v == null || ApiLevelHelper.isLowerThan(Build.VERSION_CODES.LOLLIPOP)) {
// Don't run a transition if the passed view is null
activity?.run {
navigateToCategoryActivity()
}
return
}
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
activity?.run {
window.sharedElementExitTransition.addListener(object :
Transition.TransitionListener by TransitionListenerAdapter {
override fun onTransitionEnd(transition: Transition) {
finish()
}
})
val pairs = TransitionHelper.createSafeTransitionParticipants(this, true,
Pair(v, getString(R.string.transition_avatar)))
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, *pairs)
ActivityLaunchHelper.launchCategorySelection(this, options)
}
}
}
private fun initContents() {
player?.run {
valid().let {
firstNameView?.setText(firstName)
lastInitialView?.setText(lastInitial)
avatar?.run { selectAvatar(this) }
}
}
}
private fun isAvatarSelected() = selectedAvatarView != null || selectedAvatar != null
private fun selectAvatar(avatar: Avatar) {
selectedAvatar = avatar
avatarGrid?.run {
requestFocusFromTouch()
setItemChecked(avatar.ordinal, true)
}
showFab()
}
private fun showFab() {
if (isInputDataValid()) doneFab?.show()
}
private fun isInputDataValid() =
firstNameView?.text?.isNotEmpty() == true &&
lastInitialView?.text?.isNotEmpty() == true &&
selectedAvatar != null
companion object {
private const val ARG_EDIT = "EDIT"
private const val KEY_SELECTED_AVATAR_INDEX = "selectedAvatarIndex"
fun newInstance(edit: Boolean = false): SignInFragment {
return SignInFragment().apply {
arguments = Bundle().apply {
putBoolean(ARG_EDIT, edit)
}
}
}
}
}
my app working fine any without problem , i used AsyncTask to get data json and every thing is fine . i want to add code to check internet connection in my app and i put code under onCreate in main activity .
val cm = baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected){
if (networkInfo.type == ConnectivityManager.TYPE_WIFI){
Toast.makeText(baseContext,"wifi",Toast.LENGTH_SHORT).show()
}
if (networkInfo.type == ConnectivityManager.TYPE_MOBILE){
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
}
}else {
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
this.finish()
}
when l put the phone on airplan mode and launching app he is stop working . and crash .
console log
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: com.iraqairoirt.iraqairports, PID: 10868
main activity
package com.iraqairoirt.iraqairports
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.os.AsyncTask
import android.os.Bundle
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import com.iraqairoirt.iraqairports.BaghdadAirport.ListAdapteArr
import com.iraqairoirt.iraqairports.BaghdadAirport.ListAdapteDep
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
import kotlinx.android.synthetic.main.fragment_baghdada_arrivel.*
import kotlinx.android.synthetic.main.fragment_baghdada_dep.*
import org.json.JSONArray
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import android.widget.Toast
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.support.design.widget.Snackbar
import com.iraqairoirt.iraqairports.BaghdadAirport.FlightsArrivelBeforBGW
import com.iraqairoirt.iraqairports.BaghdadAirport.FlightsDepBeforBGW
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = "airport.json"
Arr().execute(url)
setSupportActionBar(toolbar)
val fragmentAdapter = MyPagerAdapter(supportFragmentManager)
viewpager_main.adapter = fragmentAdapter
sliding_tabs.setupWithViewPager(viewpager_main)
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
val cm = baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected){
if (networkInfo.type == ConnectivityManager.TYPE_WIFI){
Toast.makeText(baseContext,"wifi",Toast.LENGTH_SHORT).show()
}
if (networkInfo.type == ConnectivityManager.TYPE_MOBILE){
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
}
}else {
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
this.finish()
}
}
// full class for json api
inner class Arr : AsyncTask<String, String, String>() {
val progressDialog = AlertDialog.Builder(this#MainActivity)
val dialogView = layoutInflater.inflate(R.layout.progress_dialog, null)
val message = dialogView.findViewById<TextView>(R.id.message_id)
val dialog = progressDialog.create()
override fun onPreExecute() {
super.onPreExecute()
dialog.setMessage("يرجى الانتظار")
dialog.setCancelable(false)
dialog.show()
}
// for build connection
override fun doInBackground(vararg url: String?): String {
var text: String
val 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)
dialog.dismiss();
}
override fun onProgressUpdate(vararg text: String?) {
}
#SuppressLint("WrongViewCast")
private fun handleJson(jsonString: String?) {
val jsonObj = JSONObject(jsonString)
val result = jsonObj.getJSONObject("result")
val response = result.getJSONObject("response")
val airport = response.getJSONObject("airport")
val pluginData = airport.getJSONObject("pluginData")
val schedule = pluginData.getJSONObject("schedule")
val arrivals = schedule.getJSONObject("arrivals")
// weather data
val weather = pluginData.getJSONObject("weather")
val mater = weather.getString("metar")
// MaterText.text=mater
// val data = arrivals.getJSONObject("data")
val jsonArray = JSONArray(arrivals.get("data").toString())
val list = ArrayList<FlightShdu>()
var x = 0
while (x < jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(x)
list.add(
FlightShdu(
jsonObject.getJSONObject("flight").getJSONObject("identification").getJSONObject("number").getString(
"default"
),
jsonObject.getJSONObject("flight").getJSONObject("airline").getString("short"),
jsonObject.getJSONObject("flight").getJSONObject("status").getJSONObject("generic").getJSONObject(
"status"
).getString("text"),
jsonObject.getJSONObject("flight").getJSONObject("airline").getJSONObject("code").getString("icao"),
jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("scheduled").getString("arrival"),
jsonObject.getJSONObject("flight").getJSONObject("airport").getJSONObject("origin").getJSONObject(
"code"
).getString("iata"),
jsonObject.getJSONObject("flight").getJSONObject("aircraft").getJSONObject("model").getString("code"),
// for more information
jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("real").getString("departure"),
jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("estimated").getString("arrival"),
// jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("estimated").getString("arrival"),
jsonObject.getJSONObject("flight").getJSONObject("aircraft").getString("registration"),
jsonObject.getJSONObject("flight").getJSONObject("status").getJSONObject("generic").getJSONObject(
"status"
).getString("diverted"),
arrivals.getString("timestamp"),
jsonObject.getJSONObject("flight").getJSONObject("status").getString("icon")
)
)
x++
}
list.forEach(::println)
var adapter = ListAdapteArr(this#MainActivity, list)
flight_arrivel_list.adapter = adapter
}
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
//noinspection SimplifiableIfStatement
if (id == R.id.flightarrbeforbgw) {
val intent = Intent(this, FlightsArrivelBeforBGW::class.java)
this.startActivity(intent)
return true
}
if (id == R.id.flightdepbefrobgw) {
val intent = Intent(this, FlightsDepBeforBGW::class.java)
this.startActivity(intent)
return true
}
//
// if (id == R.id.searchflights) {
// Toast.makeText(this, "Android Menu is Clicked", Toast.LENGTH_LONG).show()
// return true
// }
return super.onOptionsItemSelected(item)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_camera -> {
// Handle the camera action
}
R.id.nav_gallery -> {
}
R.id.nav_slideshow -> {
}
R.id.nav_manage -> {
}
R.id.nav_share -> {
}
R.id.nav_send -> {
}
}
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
to avoid fatal error and crash when there is not internet connection ,my mistake is i should put execute() url inside code of check internet connection
val cm = baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected){
if (networkInfo.type == ConnectivityManager.TYPE_WIFI){
val url = "airport.json"
Arr().execute(url)
}
if (networkInfo.type == ConnectivityManager.TYPE_MOBILE){
val url = "airport.json"
Arr().execute(url)
}
}else {
val builder = AlertDialog.Builder(this)
builder.setTitle("No internet Connection")
builder.setMessage("Please turn on internet connection to continue")
builder.setNegativeButton(
"close"
) { dialog, button -> this.finish() }
val alertDialog = builder.create()
alertDialog.show()
}
now my app working fine =)