Using Ktor, Kmongo and kotlinx.serialization together causing ClassCastException ... What am I doing wrong? - kotlin

https://github.com/reticent-monolith/winds_server is the github repo if anyone finds it easier looking there.
I'm trying to use KMongo and Ktor with Kotlin's Serialization module but creating the MongoClient results in the following exception:
java.lang.ClassCastException: class org.litote.kmongo.serialization.SerializationCodecRegistry cannot be cast to class org.bson.codecs.configuration.CodecProvider (org.litote.kmongo.serialization.SerializationCodecRegistry and org.bson.codecs.configuration.CodecProvider are in unnamed module of loader 'app')
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry(ClassMappingTypeService.kt:83)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.codecRegistry(SerializationClassMappingTypeService.kt:40)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.coreCodecRegistry(SerializationClassMappingTypeService.kt:97)
at org.litote.kmongo.service.ClassMappingType.coreCodecRegistry(ClassMappingType.kt)
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry$default(ClassMappingTypeService.kt:79)
at org.litote.kmongo.KMongo.configureRegistry$kmongo_core(KMongo.kt:79)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:70)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:57)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:47)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:39)
at com.reticentmonolith.repo.MongoDispatchRepo.<init>(MongoDispatchRepo.kt:11)
at com.reticentmonolith.ApplicationKt.<clinit>(Application.kt:14)
... 23 more
Am I missing something in my build.gradle?
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.5.10'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10'
id 'application'
}
group 'com.reticentmonolith'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"
sourceSets {
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
main.resources.srcDirs = ['resources']
test.resources.srcDirs = ['testresources']
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// KOTLIN
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// KTOR
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "io.ktor:ktor-server-core:$ktor_version"
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
// MONGO
implementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.8'
// KOTLINX SERIALIZATION
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
// KMONGO
implementation 'org.litote.kmongo:kmongo-serialization:4.2.7'
implementation group: 'org.litote.kmongo', name: 'kmongo-id-serialization', version: '4.2.7'
// KBSON (optional dependency for KMONGO and Serialization)
implementation "com.github.jershell:kbson:0.4.4"
}
Here's my MongoClient class:
import com.mongodb.client.MongoDatabase
import com.reticentmonolith.models.Dispatch
import java.time.LocalDate
import org.litote.kmongo.*
class MongoDispatchRepo: DispatchRepoInterface {
private val client = KMongo.createClient()
private val database: MongoDatabase = client.getDatabase("zw")
private val windsData = database.getCollection<Dispatch>("winds")
override fun createDispatch(dispatch: Dispatch) {
windsData.insertOne(dispatch)
}
override fun getAllDispatches(): Map<String, List<Dispatch>> {
val list = windsData.find().toList()
return mapOf("dispatches" to list)
}
override fun getDispatchesByDate(date: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date eq date).toList())
}
override fun getDispatchesByDateRange(start: LocalDate, end: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date gte(start), Dispatch::date lte(end)).toList())
}
override fun getDispatchById(id: Id<Dispatch>): Dispatch? {
return windsData.findOneById(id)
}
override fun updateDispatchById(id: Id<Dispatch>, update: Dispatch): Boolean {
val oldDispatch = getDispatchById(id)
if (oldDispatch != null) {
update.date = oldDispatch.date
update._id = oldDispatch._id
windsData.updateOneById(id, update)
return true
}
return false
}
override fun updateLastDispatch(update: Dispatch): Boolean {
val lastDispatch = getLastDispatch() ?: return false
update.date = lastDispatch.date
update._id = lastDispatch._id
windsData.updateOneById(lastDispatch._id, update)
return true
}
override fun deleteDispatchById(id: Id<Dispatch>) {
windsData.deleteOne(Dispatch::_id eq id)
}
override fun getLastDispatch(): Dispatch? {
val todaysDispatches = getDispatchesByDate(LocalDate.now())
if (todaysDispatches.isEmpty()) return null
return todaysDispatches["dispatches"]?.last()
}
override fun addSpeedsToLastDispatch(line4: Int?, line3: Int?, line2: Int?, line1: Int?) {
val lastDispatch = getLastDispatch() ?: return
lastDispatch.riders.get(4)?.speed = line4
lastDispatch.riders.get(3)?.speed = line3
lastDispatch.riders.get(2)?.speed = line2
lastDispatch.riders.get(1)?.speed = line1
updateLastDispatch(lastDispatch)
}
fun purgeDatabase() {
windsData.deleteMany(Dispatch::comment eq "")
}
}
And my Application.kt for Ktor:
package com.reticentmonolith
import com.reticentmonolith.models.createExampleDispatches
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.http.*
import com.reticentmonolith.repo.MongoDispatchRepo
import io.ktor.features.*
import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
val repo = MongoDispatchRepo()
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused") // Referenced in application.conf
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json(
Json { serializersModule = IdKotlinXSerializationModule },
contentType = ContentType.Application.Json
)
}
repo.purgeDatabase()
val dispatchList = repo.getAllDispatches()["dispatches"]
if (dispatchList != null && dispatchList.isEmpty()) {
createExampleDispatches(0).forEach {
repo.createDispatch(it)
println("######## date: ${it.date} ################")
}
}
routing {
get("/dispatches/") {
call.response.status(HttpStatusCode.OK)
val dispatches = repo.getAllDispatches()
println("Dispatches from Mongo: $dispatches")
println("Dispatches encoded for response: $dispatches")
}
}
}
And finally the class being serialized:
package com.reticentmonolith.models
import com.reticentmonolith.serializers.DateSerializer
import com.reticentmonolith.serializers.TimeSerializer
import kotlinx.serialization.Contextual
import org.litote.kmongo.*
import kotlinx.serialization.Serializable
import java.time.LocalDate
import java.time.LocalTime
#Serializable
data class Dispatch(
var riders: MutableMap<Int, #Contextual Rider?> = mutableMapOf(
1 to null,
2 to null,
3 to null,
4 to null
),
var comment: String = "",
var wind_degrees: Int,
var wind_speed: Double,
var winds_instructor: String,
var bt_radio: String,
var _id: #Contextual Id<Dispatch> = newId(),
// #Serializable(with=DateSerializer::class)
#Contextual var date: LocalDate = LocalDate.now(),
// #Serializable(with=TimeSerializer::class)
#Contextual var time: LocalTime = LocalTime.now()
)
fun createExampleDispatches(amount: Int): Collection<Dispatch> {
val dispatches = mutableListOf<Dispatch>()
for (i in 0..amount) {
dispatches.add(Dispatch(
bt_radio = "BT Instructor",
winds_instructor = "Winds Instructor",
wind_speed = 20.5,
wind_degrees = 186
).apply {
this.riders[2] = Rider(67, front_slider = Slider.BLACK, trolley = 125)
this.riders[3] = Rider(112, front_slider = Slider.NEW_RED, rear_slider = Slider.YELLOW, trolley = 34)
})
}
return dispatches
}
Any help would be hugely appreciated :) Thanks!

Related

Receiver class does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()'

I'm trying to run local server with KTOR and to cover it with tests. I wrote some code and after writing some tests, the tests successfully raised the local server and passed. However, if I try to start a local server, I get this error
Exception in thread "main" java.lang.AbstractMethodError: Receiver
class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does
not define or inherit an implementation of the resolved method
'abstract boolean getDevelopmentMode()' of interface
io.ktor.application.ApplicationEnvironment.
I attach the code and the stack trace from below
server.kt
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
gson()
}
routing {
val controller = Controller()
get("/converter{from}{to}") {
try {
val fromCurrency = call.request.queryParameters["from"]
val toCurrency = call.request.queryParameters["to"]
val rate = controller.converter(fromCurrency, toCurrency)
val response = Response(
"1 $fromCurrency = $rate $toCurrency",
null
)
call.respond(HttpStatusCode.OK, response)
} catch (e: ControllerException) {
val response = Response(
null,
e.message
)
call.respond(HttpStatusCode.BadRequest, response)
}
}
get {
call.respond(HttpStatusCode.NotFound, Response())
}
}
}
#Serializable
data class Response(
#SerialName("converterResponse")
val converterResponse: String? = null,
#SerialName("errorMessage")
val errorMessage: String? = null
)
converterTest.kt
import com.google.gson.Gson
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ConverterTest {
#Test
fun `Identical Correct Currencies`() {
withTestApplication(Application::module) {
handleRequest(HttpMethod.Get, "/converter?from=USD&to=USD").apply {
assertEquals(HttpStatusCode.OK, response.status())
val expectedResponse = Response("1 USD = 1 USD")
assertEquals(
Gson().toJson(expectedResponse),
response.content
)
}
}
}
}
stacktrace
Exception in thread "main" java.lang.AbstractMethodError: Receiver class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()' of interface io.ktor.application.ApplicationEnvironment.
at io.ktor.application.Application.<init>(Application.kt:20)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:269)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:125)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:245)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:126)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:26)
at ServerKt.main(server.kt:11)
build.gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30" // or kotlin("multiplatform") or any other kotlin plugin
application
}
group = "me.admin"
version = "1.0-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
dependencies {
val kotlin_version = "1.5.30"
val ktor_version = "1.6.3"
implementation("io.ktor:ktor-server-netty:1.4.0")
implementation("io.ktor:ktor-client-cio:1.4.0")
implementation("io.ktor:ktor-html-builder:1.4.0")
implementation("io.ktor:ktor-client-serialization:1.3.2-1.4-M2")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.slf4j:slf4j-api:2.0.0-alpha5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC")
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("io.ktor:ktor-gson:$ktor_version")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClassName = "ServerKt"
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
The dependency io.ktor:ktor-server-netty:1.4.0 causes the AbstractMethodError. To fix it just use the same version for all Ktor dependencies:
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("io.ktor:ktor-html-builder:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")

Squareup Retrofit/Moshi example process not shutting down

I have a working "hello world" App for Squareup's Retrofit2 http client (https://square.github.io/retrofit/) using JSON unmarschalling with Moshi (https://github.com/square/moshi) ...
Everything works fine, but without the System.exit(0) the App will run forever.
Any idea why this is??
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path
fun main() {
MainApp().doIt()
println("main READY!")
System.exit(0)
}
interface GitHubService {
#GET("users/{user}/repos")
fun listRepos(#Path("user") user: String): Call<List<Repo>>
}
#JsonClass(generateAdapter = true)
data class Repo(val full_name: String, val private: Boolean)
class MainApp {
fun doIt() {
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
val retrofitGithub = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val githubService = retrofitGithub.create(GitHubService::class.java)
val reposCall: Call<List<Repo>> = githubService.listRepos("HoffiMuc")
val response = reposCall.execute()
if (response.isSuccessful) {
val repos = response.body() ?: ArrayList<Repo>()
if ( repos.isNotEmpty() ) {
for (repo in repos) {
println(repo)
}
} else {
println("Response Body is null or no repo in result list")
}
} else {
println(response.headers())
}
}
}
What happens if you add an explicit dependency on the latest release of OkHttp? The old version Retrofit depends on used non-daemon threads.

Compile-time value preprocessed into Kotlin code from Gradle

I am trying to get some values from a Gradle build into some Kotlin code.
For example, if my code was:
const val compileDate = THE_DATE_THAT_THE_CODE_WAS_COMPILED_ON;
const val compileBranch = THE_GIT_BRANCH_THAT_THE_CODE_WAS_COMPILED_FROM;
fun main() {
println("$compileDate - $compileBranch")
}
Is there any way to get those constants embedded into the code from the build itself?
If its not possible in that manner, I would think it might be possible using annotations, but again - I don't know how.
Any help would be appreciated.
Thank you.
Using annotations
So I found a solution by writing an annotation processor. Here's a fairly minimal working example:
./annotations/src/main/kotlin/net/example/CompileTimeValue.kt:
package net.example
#Retention(AnnotationRetention.SOURCE)
#MustBeDocumented
annotation class CompileTimeValue(val of : String)
./annotations/build.gradle.kts:
plugins {
kotlin("jvm")
}
./processor/src/main/kotlin/net/example/CompileTimeEvaluator.kt:
package net.example
import com.google.auto.service.AutoService
import java.io.File
import java.io.IOException
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
#AutoService(Processor::class)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedOptions(CompileTimeEvaluator.KAPT_OPTION)
class CompileTimeEvaluator : AbstractProcessor() {
private val assignments = mutableMapOf<Element, String?>()
override fun getSupportedAnnotationTypes(): MutableSet<String> =
mutableSetOf(CompileTimeValue::class.java.name)
override fun getSupportedSourceVersion(): SourceVersion =
SourceVersion.latest()
override fun process(set: MutableSet<out TypeElement>?, env: RoundEnvironment?): Boolean {
env?.getElementsAnnotatedWith(CompileTimeValue::class.java)?.forEach {
val note = it.getAnnotation(CompileTimeValue::class.java) as CompileTimeValue
assignments[it] = outputOfCommand(note)
}
val kaptGeneratedDir = processingEnv.options[KAPT_OPTION]
val file = File(kaptGeneratedDir, "ComputedConstants.kt")
file.writeText(generatedClass())
return true
}
private fun generatedClass() : String = """
object ComputedConstants {
fun load() {
${loadAssignments()}
}
}
""".trimIndent()
private fun loadAssignments() : String =
assignments.entries.joinToString("\n") {
val element = it.key
val value = it.value
val elementName = element.simpleName.toString()
// TODO: I think a recursive solution to find the true parent class
// is necessary here to deal with nested classes
val elementNest = element.enclosingElement.simpleName.toString()
val elementPack = processingEnv.elementUtils.getPackageOf(element)
val fullName = "$elementPack.$elementNest.$elementName"
if (value != null)
"$fullName = \"\"\"$value\"\"\""
else
"$fullName = null"
}
private fun outputOfCommand(note : CompileTimeValue) : String? = try {
val process = ProcessBuilder(*note.of.split(' ').toTypedArray())
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
process.waitFor()
process.inputStream.bufferedReader().readText().trimEnd('\n')
} catch (e : IOException) {
null
}
companion object {
const val KAPT_OPTION = "kapt.kotlin.generated"
}
}
./processor/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
}
kapt {
correctErrorTypes = true
}
dependencies {
"kapt"(project(":annotations"))
compileOnly(project(":annotations"))
implementation(fileTree("libs").include("*.jar"))
implementation("com.google.auto.service:auto-service:1.0-rc6")
annotationProcessor("com.google.auto.service:auto-service:1.0-rc6")
}
./example/src/main/kotlin/net/example/Example.kt:
package net.example
object Constants {
#field:CompileTimeValue("date")
var compileDate : String? = "Unknown"
}
fun main() {
ComputedConstants.load()
println("Compiled on: ${Constants.compileDate}")
}
./example/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
application
}
application {
mainClass.set("net.example.ExampleKt")
}
dependencies {
implementation(project(":annotations"))
"kapt"(project(":annotations"))
"kapt"(project(":processor"))
}
./example/build/generated/source/kaptKotlin/main/ComputedConstants.kt:
object ComputedConstants {
fun load() {
net.example.Constants.compileDate = """Sat 27 Mar 2021 01:30:46 PM EDT"""
}
}
./build.gradle.kts:
plugins {
// apply(false) so that it can be re-used in children
kotlin("jvm") version "1.4.31" apply(false)
kotlin("kapt") version "1.4.31" apply(false)
}
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
}
./settings.gradle.kts:
include(":example")
include(":annotations")
include(":processor")
Another quick way is to use the gradle copy task to inflate a kotlin source template.
Create a kotlin template file App.kt some where in the source tree (src/main/templates/App.kt)
object App {
const val VERSION = "$version"
const val BASE_VERSION = "$base_version"
}
Create a copy task in your build file
val copyTemplates by registering(Copy::class) {
filteringCharset = "UTF-8"
from(project.projectDir.resolve("src/main/templates"))
into(project.buildDir.resolve("generated-sources/templates/kotlin/main"))
}
Add the generated template to the main sourceset
sourceSets {
main {
java.srcDirs(copyTemplates)
}
}
Now you can access App as regular kotlin object from your classes
fun main() {
println("Version is : ${App.VERSION}")
}

Recycler not inflated with VideoPlayerRecyclerView

I have been trying to implement a ExpoPlayer subclass on RecyclerView based on this link: https://codingwithmitch.com/blog/playing-video-recyclerview-exoplayer-android/.
I have converted the code to Kotlin and am using the Androidx libraries.
The problem is that although there are no exceptions, my code can not find the subclassed RecyclerView in the layout. Not sure if this is a real clue but when tracing the LayoutInflater, I see that it can't find android.widget.view, android.app.view, or android.webkit.view related to the recyclerview/PlayerView (It looks like the the LayoutInflater sequentially tries to find these view types dring this process)
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-android-extensions'
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
compileSdkVersion 28
defaultConfig {
applicationId "org.video"
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// to use any vector drawables
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures{
dataBinding = true
// for view binding :
//viewBinding = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
def glideVersion = "4.9.0"
def exoPlayerVersion = "2.8.4"
def androidxSupportVersion="1.1.0"
def nav_version = "2.3.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
// takes over from com.android.support:design
implementation "com.google.android.material:material:1.1.0"
implementation "androidx.recyclerview:recyclerview:$androidxSupportVersion"
// ExoPlayer - playing videos
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerVersion"
// Glide - thumbnails images
implementation "com.github.bumptech.glide:glide:$glideVersion"
kapt "com.github.bumptech.glide:compiler:$glideVersion"
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
// implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.activity:activity:1.1.0"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
// Kotlin
implementation "androidx.core:core-ktx:1.3.0"
implementation "androidx.fragment:fragment-ktx:1.2.5"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// following https://dev.to/anesabml/dagger-hilt-basics-23g8
//Hilt Dependency Injection
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha01"
implementation "com.google.dagger:hilt-android:2.28-alpha"
// For injecting ViewModel
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01"
// For injecting WorkManager
implementation "androidx.hilt:hilt-work:1.0.0-alpha01"
// ExoPlayer - playing videos
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerVersion"
// Glide - thumbnails images
implementation "com.github.bumptech.glide:glide:$glideVersion"
kapt "com.github.bumptech.glide:compiler:$glideVersion"
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Dynamic Feature Module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
// testImplementation 'junit:junit:4.12'
// androidTestImplementation 'androidx.test.ext:junit:1.1.1'
// androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
repositories {
mavenCentral()
}
watch_videos_layout.xml file
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data class="WatchVideosBinding">
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f2f2f2"
tools:context="org.video.start.StartOptionsActivity">
<org.video.VideoPlayerRecyclerView
android:id="#+id/video_player_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
VideoPlayerRecyclerView.kt
package org.video
import android.content.Context
import android.graphics.Point
import android.net.Uri
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.*
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.BandwidthMeter
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import org.video.R
// from https://codingwithmitch.com/blog/playing-video-recyclerview-exoplayer-android/ converted to Kotlin
class VideoPlayerRecyclerView #JvmOverloads constructor(context: Context, attrs: AttributeSet) : RecyclerView(context) {
private val TAG = "VideoPlayerRecyclerView"
private enum class VolumeState {
ON, OFF
}
// ui
private var thumbnail: ImageView? = null
private var volumeControl: ImageView? = null
private var progressBar: ProgressBar? = null
private var viewHolderParent: View? = null
private var frameLayout: FrameLayout? = null
private var videoSurfaceView: PlayerView? = null
private var videoPlayer: SimpleExoPlayer? = null
// vars
private var mediaObjects: ArrayList<MediaObject> = ArrayList()
private var videoSurfaceDefaultHeight = 0
private var screenDefaultHeight = 0
private var playPosition = -1
private var isVideoViewAdded = false
private var requestManager: RequestManager? = null
// controlling playback state
private var volumeState: VolumeState? = null
init {
// context = context.applicationContext
val display = (getContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
val point = Point()
display.getSize(point)
videoSurfaceDefaultHeight = point.x
screenDefaultHeight = point.y
videoSurfaceView = PlayerView(context)
videoSurfaceView?.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
val bandwidthMeter: BandwidthMeter = DefaultBandwidthMeter()
val videoTrackSelectionFactory: TrackSelection.Factory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector: TrackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
// 2. Create the player
videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
// Bind the player to the view.
videoSurfaceView?.setUseController(false)
videoSurfaceView?.setPlayer(videoPlayer)
setVolumeControl(VolumeState.ON)
addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == SCROLL_STATE_IDLE) {
Log.d(TAG, "onScrollStateChanged: called.")
if (thumbnail != null) { // show the old thumbnail
thumbnail!!.visibility = View.VISIBLE
}
// There's a special case when the end of the list has been reached.
// Need to handle that with this bit of logic
if (!recyclerView.canScrollVertically(1)) {
playVideo(true)
} else {
playVideo(false)
}
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
}
})
addOnChildAttachStateChangeListener(object : OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {}
override fun onChildViewDetachedFromWindow(view: View) {
if (viewHolderParent != null && viewHolderParent == view) {
resetVideoView()
}
}
})
this.videoPlayer?.addListener(object : Player.EventListener {
// override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {
}
override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {}
override fun onLoadingChanged(isLoading: Boolean) {}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_BUFFERING -> {
Log.e(TAG, "onPlayerStateChanged: Buffering video.")
if (progressBar != null) {
progressBar!!.visibility = View.VISIBLE
}
}
Player.STATE_ENDED -> {
Log.d(TAG, "onPlayerStateChanged: Video ended.")
videoPlayer?.seekTo(0)
}
Player.STATE_IDLE -> {
}
Player.STATE_READY -> {
Log.e(TAG, "onPlayerStateChanged: Ready to play.")
if (progressBar != null) {
progressBar!!.visibility = View.GONE
}
if (!isVideoViewAdded) {
addVideoView()
}
}
else -> {
}
}
}
override fun onRepeatModeChanged(repeatMode: Int) {}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
override fun onPlayerError(error: ExoPlaybackException) {}
override fun onPositionDiscontinuity(reason: Int) {}
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {}
override fun onSeekProcessed() {}
})
}
fun playVideo(isEndOfList: Boolean) {
var targetPosition: Int
if (!isEndOfList) {
var startPosition = (getLayoutManager() as LinearLayoutManager).findFirstVisibleItemPosition()
var endPosition = (getLayoutManager() as LinearLayoutManager).findLastVisibleItemPosition()
// if there is more than 2 list-items on the screen, set the difference to be 1
if (endPosition - startPosition > 1) {
endPosition = startPosition + 1
}
// something is wrong. return.
if (startPosition < 0 || endPosition < 0) {
return
}
// if there is more than 1 list-item on the screen
if (startPosition != endPosition) {
val startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition)
val endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition)
if (startPositionVideoHeight > endPositionVideoHeight) {
targetPosition = startPosition
} else {
targetPosition = endPosition
}
} else {
targetPosition = startPosition
}
} else {
targetPosition = mediaObjects.size - 1
}
Log.d(TAG, "playVideo: target position: " + targetPosition)
// video is already playing so return
if (targetPosition == playPosition) {
return
}
// set the position of the list-item that is to be played
this.playPosition = targetPosition
if (videoSurfaceView == null) {
return
}
// remove any old surface views from previously playing videos
videoSurfaceView?.setVisibility(INVISIBLE)
removeVideoView(videoSurfaceView!!)
val currentPosition: Int = targetPosition - (getLayoutManager() as LinearLayoutManager).findFirstVisibleItemPosition()
val child: View = getChildAt(currentPosition) ?: return
val holder = child.getTag() as VideoPlayerRecyclerAdapter.VideoPlayerViewHolder?
if (holder == null) {
playPosition = -1
return
}
this.thumbnail = holder.thumbnail
this.progressBar = holder.progressBar
this.volumeControl = holder.volumeControl
this.viewHolderParent = holder.itemView
this.requestManager = holder.requestManager
this.frameLayout = holder.media_container
videoSurfaceView!!.setPlayer(videoPlayer)
viewHolderParent!!.setOnClickListener(videoViewClickListener)
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(
context, Util.getUserAgent(context, "RecyclerView VideoPlayer"))
val mediaUrl: String? = mediaObjects.get(targetPosition).media_url
if (mediaUrl != null) {
val videoSource: MediaSource = ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(mediaUrl))
videoPlayer?.prepare(videoSource)
videoPlayer?.setPlayWhenReady(true)
}
}
private val videoViewClickListener = OnClickListener { toggleVolume() }
/**
* Returns the visible region of the video surface on the screen.
* if some is cut off, it will return less than the #videoSurfaceDefaultHeight
* #param playPosition
* #return
*/
private fun getVisibleVideoSurfaceHeight(playPosition: Int): Int {
val at = playPosition - (layoutManager as LinearLayoutManager?)!!.findFirstVisibleItemPosition()
Log.d(TAG, "getVisibleVideoSurfaceHeight: at: $at")
val child = getChildAt(at) ?: return 0
val location = IntArray(2)
child.getLocationInWindow(location)
return if (location[1] < 0) {
location[1] + videoSurfaceDefaultHeight
} else {
screenDefaultHeight - location[1]
}
}
// Remove the old player
private fun removeVideoView(videoView: PlayerView) {
val parent: ViewGroup? = videoView.parent as ViewGroup
if (parent == null){
return;
}
val index: Int = parent.indexOfChild(videoView)
if (index >= 0) {
parent.removeViewAt(index)
isVideoViewAdded = false
viewHolderParent!!.setOnClickListener(null)
}
}
private fun addVideoView() {
frameLayout!!.addView(videoSurfaceView)
isVideoViewAdded = true
videoSurfaceView!!.requestFocus()
videoSurfaceView!!.visibility = View.VISIBLE
videoSurfaceView!!.alpha = 1f
thumbnail!!.visibility = View.GONE
}
private fun resetVideoView() {
if (isVideoViewAdded) {
removeVideoView(videoSurfaceView!!)
playPosition = -1
videoSurfaceView!!.visibility = View.INVISIBLE
thumbnail!!.visibility = View.VISIBLE
}
}
fun releasePlayer() {
if (videoPlayer != null) {
videoPlayer!!.release()
videoPlayer = null
}
viewHolderParent = null
}
private fun toggleVolume() {
if (videoPlayer != null) {
if (volumeState === VolumeState.OFF) {
Log.d(TAG, "togglePlaybackState: enabling volume.")
setVolumeControl(VolumeState.ON)
} else if (volumeState === VolumeState.ON) {
Log.d(TAG, "togglePlaybackState: disabling volume.")
setVolumeControl(VolumeState.OFF)
}
}
}
private fun setVolumeControl(state: VolumeState) {
volumeState = state
if (state === VolumeState.OFF) {
videoPlayer!!.volume = 0f
animateVolumeControl()
} else if (state === VolumeState.ON) {
videoPlayer!!.volume = 1f
animateVolumeControl()
}
}
private fun animateVolumeControl() {
if (volumeControl != null) {
volumeControl!!.bringToFront()
if (volumeState === VolumeState.OFF) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
requestManager?.load(getResources().getDrawable(R.drawable.ic_volume_off_gray_24dp, null))
?.into(volumeControl!!)
}
} else if (volumeState === VolumeState.ON) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
requestManager?.load(getResources().getDrawable(R.drawable.ic_volume_up_grey_24dp, null))
?.into(volumeControl!!)
}
}
volumeControl!!.animate().cancel()
volumeControl!!.alpha = 1f
volumeControl!!.animate()
.alpha(0f)
.setDuration(600).startDelay = 1000
}
}
fun setMediaObjects(mediaObjects: ArrayList<MediaObject>) {
this.mediaObjects = mediaObjects
}
}
Fragment
package org.video
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.request.RequestOptions
import dagger.hilt.android.AndroidEntryPoint
import org.video.R
import org.video.databinding.WatchVideosBinding
import java.util.*
import kotlin.collections.ArrayList
// from https://codingwithmitch.com/blog/playing-video-recyclerview-exoplayer-android/
// Was from MainActivity but logic moved to Fragment
#AndroidEntryPoint
class WatchVideosFragment : Fragment() {
private val videoViewModel: VideoViewModel by viewModels()
lateinit var binding: WatchVideosBinding
var recyclerView: VideoPlayerRecyclerView? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.watch_videos_layout,
container,
false
)
this.recyclerView = binding.videoPlayerRecyclerView <<< comes back null
initRecyclerView(this.recyclerView)
return binding.root
}
private fun initRecyclerView(recyclerView: VideoPlayerRecyclerView?) {
recyclerView?.layoutManager = LinearLayoutManager(context)
recyclerView?.addItemDecoration(VerticalSpacingItemDecorator(10))
val mediaObjects: ArrayList<MediaObject> = videoViewModel.getWatchMediaObjects()
recyclerView?.setMediaObjects(mediaObjects)
recyclerView?.adapter = VideoPlayerRecyclerAdapter(mediaObjects, initGlide())
}
private fun initGlide(): RequestManager {
val options: RequestOptions = RequestOptions()
.placeholder(R.drawable.white_background)
.error(R.drawable.white_background)
return Glide.with(this)
.setDefaultRequestOptions(options)
}
override fun onDestroy() {
if (recyclerView != null) {
recyclerView?.releasePlayer()
}
super.onDestroy()
}
}
Found the problem. When I converted the code to Kotlin there was a reference to Context that I thought was redundant and deleted. My bad! Just for reference my new VideoPlayerRecyclerView is below.
The code also includes some view name changes and I found I needed to include a reference to the resource file because if I didn't I kept getting compile errors saying the R.id.(view names) could not be found. Not sure why that is.
package org.video
import android.content.Context
import android.graphics.Point
import android.net.Uri
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.*
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.BandwidthMeter
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import org.video.R
import java.util.*
class VideoPlayerRecyclerView : RecyclerView {
private enum class VolumeState {
ON, OFF
}
// ui
private var thumbnail: ImageView? = null
private var volumeControl: ImageView? = null
private var progressBar: ProgressBar? = null
private var viewHolderParent: View? = null
private var frameLayout: FrameLayout? = null
private var videoSurfaceView: PlayerView? = null
private var videoPlayer: SimpleExoPlayer? = null
// vars
private var mediaObjects = ArrayList<MediaObject>()
private var videoSurfaceDefaultHeight = 0
private var screenDefaultHeight = 0
private var viewContext: Context? = null //<< Need this. Renamed to avoid name conflict with constructor context name
private var playPosition = -1
private var isVideoViewAdded = false
private var requestManager: RequestManager? = null
// controlling playback state
private var volumeState: VolumeState? = null
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
private fun init(context: Context) {
this.viewContext = context.applicationContext
val display = (getContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
val point = Point()
display.getSize(point)
videoSurfaceDefaultHeight = point.x
screenDefaultHeight = point.y
videoSurfaceView = PlayerView(this.viewContext)
videoSurfaceView!!.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
val bandwidthMeter: BandwidthMeter = DefaultBandwidthMeter()
val videoTrackSelectionFactory: TrackSelection.Factory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector: TrackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
// 2. Create the player
videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
// Bind the player to the view.
videoSurfaceView!!.useController = false
videoSurfaceView!!.player = videoPlayer
setVolumeControl(VolumeState.ON)
addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == SCROLL_STATE_IDLE) {
Log.d(TAG, "onScrollStateChanged: called.")
if (thumbnail != null) { // show the old thumbnail
thumbnail!!.visibility = View.VISIBLE
}
// There's a special case when the end of the list has been reached.
// Need to handle that with this bit of logic
if (!recyclerView.canScrollVertically(1)) {
playVideo(true)
} else {
playVideo(false)
}
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
}
})
addOnChildAttachStateChangeListener(object : OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {}
override fun onChildViewDetachedFromWindow(view: View) {
if (viewHolderParent != null && viewHolderParent == view) {
resetVideoView()
}
}
})
this.videoPlayer?.addListener(object : Player.EventListener {
override fun onTimelineChanged(timeline: Timeline, manifest: Any?, reason: Int) {}
override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {}
override fun onLoadingChanged(isLoading: Boolean) {}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_BUFFERING -> {
Log.e(TAG, "onPlayerStateChanged: Buffering video.")
if (progressBar != null) {
progressBar!!.visibility = View.VISIBLE
}
}
Player.STATE_ENDED -> {
Log.d(TAG, "onPlayerStateChanged: Video ended.")
videoPlayer?.seekTo(0)
}
Player.STATE_IDLE -> {
}
Player.STATE_READY -> {
Log.e(TAG, "onPlayerStateChanged: Ready to play.")
if (progressBar != null) {
progressBar!!.visibility = View.GONE
}
if (!isVideoViewAdded) {
addVideoView()
}
}
else -> {
}
}
}
override fun onRepeatModeChanged(repeatMode: Int) {}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
override fun onPlayerError(error: ExoPlaybackException) {}
override fun onPositionDiscontinuity(reason: Int) {}
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {}
override fun onSeekProcessed() {}
})
}
fun playVideo(isEndOfList: Boolean) {
val targetPosition: Int
if (!isEndOfList) {
val startPosition = (layoutManager as LinearLayoutManager?)!!.findFirstVisibleItemPosition()
var endPosition = (layoutManager as LinearLayoutManager?)!!.findLastVisibleItemPosition()
// if there is more than 2 list-items on the screen, set the difference to be 1
if (endPosition - startPosition > 1) {
endPosition = startPosition + 1
}
// something is wrong. return.
if (startPosition < 0 || endPosition < 0) {
return
}
// if there is more than 1 list-item on the screen
targetPosition = if (startPosition != endPosition) {
val startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition)
val endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition)
if (startPositionVideoHeight > endPositionVideoHeight) startPosition else endPosition
} else {
startPosition
}
} else {
targetPosition = mediaObjects.size - 1
}
Log.d(TAG, "playVideo: target position: $targetPosition")
// video is already playing so return
if (targetPosition == playPosition) {
return
}
// set the position of the list-item that is to be played
playPosition = targetPosition
if (videoSurfaceView == null) {
return
}
// remove any old surface views from previously playing videos
videoSurfaceView!!.visibility = View.INVISIBLE
removeVideoView(videoSurfaceView)
val currentPosition = targetPosition - (layoutManager as LinearLayoutManager?)!!.findFirstVisibleItemPosition()
val child = getChildAt(currentPosition) ?: return
val holder = child.tag as VideoPlayerViewHolder?
if (holder == null) {
playPosition = -1
return
}
thumbnail = holder.thumbnail
progressBar = holder.progressBar
volumeControl = holder.volumeControl
viewHolderParent = holder.itemView
requestManager = holder.requestManager
frameLayout = holder.itemView.findViewById(R.id.watch_video_media_container)
videoSurfaceView!!.player = videoPlayer
viewHolderParent!!.setOnClickListener(videoViewClickListener)
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(
viewContext, Util.getUserAgent(viewContext, "RecyclerView VideoPlayer"))
val mediaUrl = mediaObjects[targetPosition].media_url
if (mediaUrl != null) {
val videoSource: MediaSource = ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(mediaUrl))
videoPlayer!!.prepare(videoSource)
videoPlayer!!.playWhenReady = true
}
}
private val videoViewClickListener = OnClickListener { toggleVolume() }
/**
* Returns the visible region of the video surface on the screen.
* if some is cut off, it will return less than the #videoSurfaceDefaultHeight
* #param playPosition
* #return
*/
private fun getVisibleVideoSurfaceHeight(playPosition: Int): Int {
val at = playPosition - (layoutManager as LinearLayoutManager?)!!.findFirstVisibleItemPosition()
Log.d(TAG, "getVisibleVideoSurfaceHeight: at: $at")
val child = getChildAt(at) ?: return 0
val location = IntArray(2)
child.getLocationInWindow(location)
return if (location[1] < 0) {
location[1] + videoSurfaceDefaultHeight
} else {
screenDefaultHeight - location[1]
}
}
// Remove the old player
private fun removeVideoView(videoView: PlayerView?) {
if (videoView?.parent == null) {
return
}
val parent = videoView?.parent as ViewGroup
val index = parent.indexOfChild(videoView)
if (index >= 0) {
parent.removeViewAt(index)
isVideoViewAdded = false
viewHolderParent!!.setOnClickListener(null)
}
}
private fun addVideoView() {
frameLayout!!.addView(videoSurfaceView)
isVideoViewAdded = true
videoSurfaceView!!.requestFocus()
videoSurfaceView!!.visibility = View.VISIBLE
videoSurfaceView!!.alpha = 1f
thumbnail!!.visibility = View.GONE
}
private fun resetVideoView() {
if (isVideoViewAdded) {
removeVideoView(videoSurfaceView)
playPosition = -1
videoSurfaceView!!.visibility = View.INVISIBLE
thumbnail!!.visibility = View.VISIBLE
}
}
fun releasePlayer() {
if (videoPlayer != null) {
videoPlayer!!.release()
videoPlayer = null
}
viewHolderParent = null
}
private fun toggleVolume() {
if (videoPlayer != null) {
if (volumeState == VolumeState.OFF) {
Log.d(TAG, "togglePlaybackState: enabling volume.")
setVolumeControl(VolumeState.ON)
} else if (volumeState == VolumeState.ON) {
Log.d(TAG, "togglePlaybackState: disabling volume.")
setVolumeControl(VolumeState.OFF)
}
}
}
private fun setVolumeControl(state: VolumeState) {
volumeState = state
if (state == VolumeState.OFF) {
videoPlayer!!.volume = 0f
animateVolumeControl()
} else if (state == VolumeState.ON) {
videoPlayer!!.volume = 1f
animateVolumeControl()
}
}
private fun animateVolumeControl() {
if (volumeControl != null) {
volumeControl!!.bringToFront()
if (volumeState == VolumeState.OFF) {
requestManager!!.load(R.drawable.ic_volume_off_grey_24dp)
.into(volumeControl!!)
} else if (volumeState == VolumeState.ON) {
requestManager!!.load(R.drawable.ic_volume_up_grey_24dp)
.into(volumeControl!!)
}
volumeControl!!.animate().cancel()
volumeControl!!.alpha = 1f
volumeControl!!.animate()
.alpha(0f)
.setDuration(600).startDelay = 1000
}
}
fun setMediaObjects(mediaObjects: ArrayList<MediaObject>) {
this.mediaObjects = mediaObjects
}
companion object {
private const val TAG = "VideoPlayerRecyclerView"
}
}

fatel error check internet connection in kotlin

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 =)