I want game to be set to a new instance of Game and I want to pass this into game. SO this is what I have.
var game: Game
init {
game = Game(this)
}
I have also tried
var game: Game = Game(this)
both threw a NullPointer at run time but it seems fine in Intellij and has no problem compiling. What am I doing wrong?
Stack Trace
org.bukkit.plugin.InvalidPluginException: java.lang.NullPointerException
at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:135) ~[server.jar:git-Spigot-21fe707-e1ebe52]
at org.bukkit.plugin.SimplePluginManager.loadPlugin(SimplePluginManager.java:329) ~[server.jar:git-Spigot-21fe707-e1ebe52]
at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:251) [server.jar:git-Spigot-21fe707-e1ebe52]
at org.bukkit.craftbukkit.v1_8_R3.CraftServer.loadPlugins(CraftServer.java:292) [server.jar:git-Spigot-21fe707-e1ebe52]
at net.minecraft.server.v1_8_R3.DedicatedServer.init(DedicatedServer.java:198) [server.jar:git-Spigot-21fe707-e1ebe52]
at net.minecraft.server.v1_8_R3.MinecraftServer.run(MinecraftServer.java:525) [server.jar:git-Spigot-21fe707-e1ebe52]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_121]
Caused by: java.lang.NullPointerException
at org.bukkit.plugin.SimplePluginManager.registerEvents(SimplePluginManager.java:523) ~[server.jar:git-Spigot-21fe707-e1ebe52]
at me.darkpaladin.uhc.game.gameSettings.GameSettingsManager.addGameSettings(GameSettingsManager.kt:43) ~[?:?]
at me.darkpaladin.uhc.game.gameSettings.GameSettingsManager.<init>(GameSettingsManager.kt:20) ~[?:?]
at me.darkpaladin.uhc.game.Game.<init>(Game.kt:49) ~[?:?]
at me.darkpaladin.uhc.UHC.<init>(UHC.kt:20) ~[?:?]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:1.8.0_121]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[?:1.8.0_121]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:1.8.0_121]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[?:1.8.0_121]
at java.lang.Class.newInstance(Class.java:442) ~[?:1.8.0_121]
at org.bukkit.plugin.java.PluginClassLoader.<init>(PluginClassLoader.java:76) ~[server.jar:git-Spigot-21fe707-e1ebe52]
at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:131) ~[server.jar:git-Spigot-21fe707-e1ebe52]
... 6 more
UHC Class
var game: Game
init {
game = Game(this)
}
override fun onReady() {
// new Lobby(Bukkit.getWorld("world"), this, false);
TeamManager()
registerCommands()
}
fun registerCommands() {
val c = commandHandler
c.addCommand(
TeamCommand(),
SetGameTypeCommand(game),
ClaimHostCommand(game),
StartGameCommand(game),
ScenarioManagerCommand(game),
KillTopCommand(game),
ConfigCommand(game),
ScatterCommand(game)
)
}
fun registerListeners() {
val p = Bukkit.getPluginManager()
p.registerEvents(GamePlayerListener(game), this)
}
Game Class
package me.darkpaladin.uhc.game
import me.darkpaladin.core.Core
import me.darkpaladin.core.utils.CoreUtils
import me.darkpaladin.core.utils.PacketUtils
import me.darkpaladin.uhc.UHC
import me.darkpaladin.uhc.events.GameStartEvent
import me.darkpaladin.uhc.events.GameStopEvent
import me.darkpaladin.uhc.game.gameEvents.GameEvent
import me.darkpaladin.uhc.game.gameEvents.GameEventRunnable
import me.darkpaladin.uhc.game.gamePlayers.GamePlayer
import me.darkpaladin.uhc.game.gamePlayers.GamePlayerManager
import me.darkpaladin.uhc.game.gameSettings.GameSettingsManager
import me.darkpaladin.uhc.scenarios.ScenarioManager
import org.apache.commons.lang.WordUtils
import org.bukkit.*
import org.bukkit.entity.Item
import org.bukkit.entity.Monster
import org.bukkit.entity.Player
import org.bukkit.scheduler.BukkitRunnable
import java.util.*
/**
* Created by Caleb on 4/28/2017.
*/
class Game(private val plugin: UHC) {
var gameType = GameType.NORMAL
var gameState = GameState.SETTING_UP
var gameStartTicks = (CoreUtils.ticksPerSecond * 10).toLong()
var finalHealTime = (CoreUtils.ticksPerSecond * 15).toLong()
var pvpTicks = (CoreUtils.ticksPerSecond * 20).toLong()
var meetupTicks = (CoreUtils.ticksPerSecond * 25).toLong()
var isBorderShrink = true
var episodeLength = (CoreUtils.ticksPerMinute * 20).toLong()
private var episode = 1
private val worlds = ArrayList<World>()
private val gameEvents = ArrayList<GameEvent>()
var gameTicks: Long = 0L
var hostUuid: UUID? = null
val scenarioManager: ScenarioManager = ScenarioManager(this)
val gamePlayerManager: GamePlayerManager = GamePlayerManager()
val gameSettingsManager: GameSettingsManager = GameSettingsManager(this)
private val instance: Game
init {
worlds.add(Bukkit.getWorld("world")) //TODO: remove
instance = this
}
val overworld: World?
get() = getWorldWithEnvironment(World.Environment.NORMAL)
val nether: World?
get() = getWorldWithEnvironment(World.Environment.NETHER)
val end: World?
get() = getWorldWithEnvironment(World.Environment.THE_END)
fun getWorldWithEnvironment(environment: World.Environment): World? {
return worlds.firstOrNull { it.environment == environment }
}
fun addGameEvent(vararg events: GameEvent) {
gameEvents.addAll(Arrays.asList(*events))
}
val host: Player
get() = Bukkit.getPlayer(hostUuid)
fun start() {
gameState = GameState.STARTING
val finalHeal = GameEvent("Final Heal", finalHealTime, object : GameEventRunnable(this) {
override fun run() {
Bukkit.getOnlinePlayers().forEach { player -> player.health = player.maxHealth }
CoreUtils.broadcast(Core.PREFIX + "Final Heal has been given. This is the FINAL heal. Do not ask for more.")
}
})
val pvp = GameEvent("PvP", pvpTicks, object : GameEventRunnable(this) {
override fun run() {
for (world in worlds) {
world.pvp = true
world.setGameRuleValue("doMobSpawning", "true")
}
}
})
val starting = GameEvent("Starting in", gameStartTicks, object : GameEventRunnable(this) {
override fun run() {
gameTicks = 0L
addGameEvent(finalHeal, pvp)
if (isBorderShrink)
addGameEvent(BorderShrinkGameEvent(meetupTicks, instance))
for (player in Bukkit.getOnlinePlayers()) {
player.health = player.maxHealth
player.foodLevel = 20
player.saturation = 20f
player.level = 0
player.exp = 0f
player.totalExperience = 0
player.closeInventory()
player.inventory.clear()
player.inventory.armorContents = null
player.gameMode = GameMode.SURVIVAL
player.activePotionEffects.forEach { potionEffect -> player.removePotionEffect(potionEffect.type) }
}
for (world in worlds) {
for (entity in world.entities) {
if (entity is Item || entity is Monster) {
entity.remove()
}
}
world.pvp = false
world.difficulty = Difficulty.HARD
world.time = 20
world.setSpawnFlags(false, false)
}
Bukkit.getOnlinePlayers().forEach { player -> gamePlayerManager.addGamePlayers(GamePlayer(player.uniqueId)) }
val players = ArrayList<Player>()
gamePlayerManager.aliveGamePlayers
.filter { gamePlayer -> gamePlayer.player != null }
.forEach { gamePlayer -> players.add(gamePlayer.player) }
scenarioManager.giveStartingItems(players)
gameState = GameState.RUNNING
Bukkit.getPluginManager().callEvent(GameStartEvent(instance))
}
})
addGameEvent(starting)
GameTimer().runTaskTimer(plugin, 0, 1)
if (gameType === GameType.RECORDED)
EpisodeTimerTask()
}
val nextEvent: GameEvent?
get() {
val events = gameEvents
val times = HashMap<GameEvent, Long>()
events.forEach { gameEvent -> times.put(gameEvent, gameEvent.time) }
var nextEvent: GameEvent? = null
var nextEventTime: Long = 999999999999999999L
for (event in times.keys) {
if (event.time < nextEventTime) {
nextEvent = event
nextEventTime = event.time
}
}
return nextEvent
}
fun getNextBorderRadius(world: World): Int {
return Math.round(world.worldBorder.size / 2 * .75).toInt()
}
private inner class GameTimer : BukkitRunnable() {
override fun run() {
val nextEvent = nextEvent
if (nextEvent != null) {
if (gameTicks!! % 20 == 0L) {
PacketUtils.sendAction(Core.HIGHLIGHTED_COLOR.toString() + ChatColor.BOLD.toString() + WordUtils.capitalizeFully(nextEvent.name) + Core.EXTRA_COLOR + " » " + Core.HIGHLIGHTED_COLOR + CoreUtils.formatTicks(nextEvent.time - gameTicks!!))
}
if (gameTicks >= nextEvent.time) {
nextEvent.runnable!!.run()
gameEvents.remove(nextEvent)
}
}
gameTicks++
}
}
private inner class BorderShrinkGameEvent(time: Long, game: Game) : GameEvent("Border Shrink", time, object : GameEventRunnable(game) {
override fun run() {
for (world in worlds) {
val wb = world.worldBorder
val radius = Math.round(wb.size / 2).toInt()
val newRadius = Math.round(wb.size / 2 * .75).toInt()
if (newRadius < 50)
wb.size = 100.0
else
wb.size = (newRadius * 2).toDouble()
CoreUtils.broadcast(Core.PREFIX + "The border has shrunk from " + radius + " to " + Math.round(wb.size / 2).toInt() + "!")
if (wb.size > 100) {
game.addGameEvent(BorderShrinkGameEvent(CoreUtils.ticksPerSecond * 5 + game.gameTicks!!, game))
}
}
}
})
private inner class EpisodeTimerTask : BukkitRunnable() {
private var ticks: Long = 0
init {
ticks = episodeLength
}
override fun run() {
if (ticks <= 0) {
cancel()
CoreUtils.broadcast(Core.PREFIX + "End of episode " + episode + ". Start episode " + episode + 1)
episode++
return
}
ticks--
}
}
fun stop() {
scenarioManager.disableAll()
Bukkit.getPluginManager().callEvent(GameStopEvent(this))
plugin.game = Game(plugin)
}
}
Game Setting Manager
package me.darkpaladin.uhc.game.gameSettings
import me.darkpaladin.core.Core
import me.darkpaladin.uhc.game.Game
import org.bukkit.Bukkit
import org.bukkit.event.Listener
import java.util.ArrayList
import java.util.Arrays
import java.util.stream.Collectors
/**
* Created by caleb on 5/6/17.
*/
class GameSettingsManager(game: Game) {
private val gameSettings = ArrayList<GameSetting>()
init {
addGameSettings(
FriendlyFireGameSetting(game),
NotchApplesGameSetting(),
DoubleArrowsGameSetting()
)
}
fun getGameSettings(): List<GameSetting> {
return gameSettings
}
val toggleableGameSettings: List<ToggleableGameSetting>
get() {
val settings = ArrayList<ToggleableGameSetting>()
for (setting in getGameSettings())
if (setting is ToggleableGameSetting)
settings.add(setting)
return settings
}
fun addGameSettings(vararg gameSettings: GameSetting) {
this.gameSettings.addAll(Arrays.asList(*gameSettings))
for(setting in gameSettings)
Bukkit.getPluginManager().registerEvents(setting as Listener, Core.instance)
}
fun getGameSetting(name: String): GameSetting {
return gameSettings.stream()
.filter { gameSetting -> gameSetting.name.equals(name, ignoreCase = true) }
.collect(Collectors.toList<GameSetting>())[0]
}
}
On line 43 of the GameSettingsManager:
fun addGameSettings(vararg gameSettings: GameSetting) {
this.gameSettings.addAll(Arrays.asList(*gameSettings))
for(setting in gameSettings) //line 43 is the one below this
Bukkit.getPluginManager().registerEvents(setting as Listener, Core.instance)
}
One of your setting objects that is being passed into Bukkit.getPluginManager() is null.
Or Core.Instance is null.
One other possibility is that the object being returned by getPluginManager() has an unitialized variable inside it that is being referenced in the registerEvents() function.
But the most likely case is that one of your setting objects is null or Core.Instance is null.
Edit
https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/plugin/SimplePluginManager.java
Here is the registerEvents() function from the SimplePluginManager in the bukkit library that gets run:
public void registerEvents(Listener listener, Plugin plugin) {
if (!plugin.isEnabled()) {
//the next line is 523
throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
}
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {
getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
}
}
According to your stack trace line 523 is the one throwing the null pointer exception. So something there is null. The listener variable is coming from your gameSettings array though.
Looking at your code there really shouldn't be a null in that gameSettings varargs array. But the function that throws the error is saying otherwise. It would be a good idea to debug to that location or do a println as your looping through the gameSettings array to verify that none of it is null.
Related
I get null on object reference. I have a default value set(not sure if this has an effect). My DataState is in a Arraylist which is shown in a Recyclerview. Datastate is changed when a button is clicked and reflect on a textview.
My datestate class:
enum class DataState {
Unselected,
Success,
Failure
}
My Arraylist:
class Tripsheetlist (var videos: ArrayList<DataModel>)
class DataModel(
var Tripsheet: Int,
var WOrder: Int,
var DElNote: Int,
var name: String,
var Weight: Int,
var tvdone: String,
var Drivers: String,
var state: DataState = DataState.Unselected
)
My Adapter:
if ( rowPos != 0){
val modal = tripsheetlist.videos[rowPos -1]
holder.txttvdone.apply {
setBackgroundResource(when (modal.state) {
DataState.Unselected -> android.R.color.transparent
DataState.Success -> R.color.green
DataState.Failure -> R.color.orange
})
text = when (modal.state) {
DataState.Unselected -> ""
DataState.Success -> "✓"
DataState.Failure -> "x"
//this is where I add code to export data through api maybe add it in the datastate set where it is success and Failure
}
}
holder.apply {
txtbutton1.setOnClickListener {
Log.e("Clicked", "Successful delivery")
//this is where I add code to export data through api
modal.state = DataState.Success
notifyDataSetChanged()
}
txtbutton2.setOnClickListener {
Log.e("Clicked", "Exception on delivery")
modal.state = DataState.Failure
notifyDataSetChanged()
}
}}
I get the error on the line setBackgroundResource(when (modal.state) {
Error :
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.dispatch.tripsheet, PID: 12935
java.lang.NullPointerException: Attempt to invoke virtual method 'int com.dispatch.tripsheet.DataState.ordinal()' on a null object reference
at com.dispatch.tripsheet.TableViewAdapter.onBindViewHolder(TableViewAdapter.kt:162)
at com.dispatch.tripsheet.TableViewAdapter.onBindViewHolder(TableViewAdapter.kt:12)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7254)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7337)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6194)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6460)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6300)
Below will be my full adapter and Main class.
Adapter:
package com.dispatch.tripsheet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.table_list_item.view.*
class TableViewAdapter(
var tripsheetlist: Tripsheetlist,
val driver: String,
var tvHeader: TextView,
val spnDriver: Spinner,
var adapter: ArrayAdapter<String>,
) : RecyclerView.Adapter<TableViewAdapter.RowViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RowViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.table_list_item, parent, false)
var i: Int = 0
var totT: Int = 1
var a: Int = 0
var b: Int = 0
var c: Int = 0
var l: Int = tripsheetlist.videos.size -1
while (i <= l){
if (driver == tripsheetlist.videos[i].Drivers) {
if (a == 0){
a = tripsheetlist.videos[i].Tripsheet}
totT = totT
if(a != tripsheetlist.videos[i].Tripsheet) {
if (b == 0){
b = tripsheetlist.videos[i].Tripsheet}
if ( totT != 3){
totT = 2}
if(b != tripsheetlist.videos[i].Tripsheet) {
c = tripsheetlist.videos[i].Tripsheet
totT = 3
}
}
}
i++
}
when (totT)
{
1 -> tvHeader.setText("Vulcan Steel Trip Sheet: " +a)
2-> tvHeader.setText("Vulcan Steel Trip Sheet: " + a + " & " + b )
3-> tvHeader.setText("Vulcan Steel Trip Sheet: " + a + " & " + b + " & " + c)
else -> tvHeader.setText("Vulcan Steel Trip Sheets")
}
var h =0
// val driverList = tripsheetlist?.videos.distinctBy { it.Drivers }
// while (h <= driverList.size){
// adapter.add(driverList[h].Drivers.toString())
// h++
// }
//used for testing until we have driver data
while (h <= 4){
adapter.add("driverList[h].Drivers.toString()"+h)
h++
}
spnDriver.adapter = adapter
return RowViewHolder(itemView) }
private fun updateData(data: ArrayList<DataModel>) {
tripsheetlist.videos = data
notifyDataSetChanged()
}
override fun getItemCount(): Int { return tripsheetlist.videos.size + 1 // one more to add header row
}
override fun onBindViewHolder(holder: RowViewHolder, position: Int) {
val rowPos = holder.adapterPosition
if (rowPos == 0) {
holder.itemView.apply {
setHeaderBg(txtWOrder)
setHeaderBg(txtDElNote)
setHeaderBg(txtCompany)
setHeaderBg(txtWeight)
setHeaderBg(txttvdone)
txtWOrder.text = "WOrder"
txtDElNote.text = "DElNote"
txtCompany.text = "Compan"
txtWeight.text = "Weight"
txttvdone.text = "?"
}
} else {
val modal = tripsheetlist.videos[rowPos -1]
holder.itemView.apply {
setContentBg(txtWOrder)
setContentBg(txtDElNote)
setContentBg(txtCompany)
setContentBg(txtWeight)
txtWOrder.text = modal.WOrder.toString()
txtDElNote.text = modal.DElNote.toString()
txtCompany.text = modal.name //company
txtWeight.text = modal.Weight.toString()
// used when we have driver data
// if (modal.Drivers == driver) {
// txtWOrder.text = modal.WOrder.toString()
// txtDElNote.text = modal.DElNote.toString()
// txtCompany.text = modal.name
// txtWeight.text = modal.Weight.toString()
//
// }
}
}
//used for testing to look in concsole for responeses - all return null
// println(tripsheetlist.videos[rowPos ].name) //name needs to be chagned to company
// println("where we need to see the state")
// println("-1")
// println(tripsheetlist.videos[rowPos ].state)
// println("0")
// println(tripsheetlist.videos[0].state)
// println("+1")
// println(tripsheetlist.videos[1 ].state)
var p = tripsheetlist.videos[1].state
println("state")
println(p)
if ( rowPos != 0){
val modal = tripsheetlist.videos[rowPos -1]
holder.txttvdone.apply {
setBackgroundResource(when (modal.state) {
DataState.Unselected -> android.R.color.transparent
DataState.Success -> R.color.green
DataState.Failure -> R.color.orange
})
text = when (modal.state) {
DataState.Unselected -> ""
DataState.Success -> "✓"
DataState.Failure -> "x"
//this is where I add code to export data through api maybe add it in the datastate set where it is success and Failure
}
}
holder.apply {
txtbutton1.setOnClickListener {
Log.e("Clicked", "Successful delivery")
//this is where I add code to export data through api
modal.state = DataState.Success
notifyDataSetChanged()
}
txtbutton2.setOnClickListener {
Log.e("Clicked", "Exception on delivery")
modal.state = DataState.Failure
notifyDataSetChanged()
}
}}
}
class RowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val txttvdone:TextView = itemView.findViewById<TextView>(R.id.txttvdone)
val txtbutton1:Button = itemView.findViewById<Button>(R.id.txtbutton1)
val txtbutton2:Button = itemView.findViewById<Button>(R.id.txtbutton2)
}
private fun setHeaderBg(view: View) {
view.setBackgroundResource(R.drawable.table_header_cell_bg)
}
private fun setContentBg(view: View) {
view.setBackgroundResource(R.drawable.table_content_cell_bg)
}
}
Main class:
package com.dispatch.tripsheet
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.*
import java.io.IOException
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerViewTripsheetlist.layoutManager = LinearLayoutManager(this)
val spnDriver: Spinner = findViewById(R.id.spnDriver)
var adapter = ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
fetchJson( adapter, spnDriver)
}
private fun fetchJson(adapter: ArrayAdapter<String>, spnDriver: Spinner){
println("Attempting to Fetch JSON")
val url = "https://api.letsbuildthatapp.com/youtube/home_feed"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
println("Failed to execute request") }
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val tripsheetlist = gson.fromJson(body, Tripsheetlist::class.java)
//remove the following later
tripsheetlist.videos.apply {
add(DataModel(190617, 182832, 1, "100",20, "Delivery not done",""))
add(DataModel(190616, 182833, 2, "100",10, "Exceptions",""))
add(DataModel(190616, 182832, 3, "100",100, "50%",""))
}
weightsum(tvTotalweight, tripsheetlist)
totaldelNotes(tvTotaldelv,tripsheetlist)
var driver : String = Spinner(tripsheetlist, adapter)
runOnUiThread {
recyclerViewTripsheetlist.adapter = TableViewAdapter(tripsheetlist, driver, tvHeader, spnDriver, adapter)
}
}
})
}
private fun Spinner(tripsheetlist: Tripsheetlist, adapter: ArrayAdapter<String>): String {
var driver : String = ""
var item : String = ""
var rowTot : Int = tripsheetlist?.videos.size
spnDriver.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
var rowPos : Int = 0
while (rowPos != rowTot){
//check which one of the following works
// item = tripsheetlist.videos[p2].Drivers.toString()
item = p2.toString()
rowPos = rowTot
}
Toast.makeText(this#MainActivity, "Driver $item selected", Toast.LENGTH_SHORT).show()
driver = item
}
override fun onNothingSelected(p0: AdapterView<*>?) {
driver = ""
//empty
}
}
return driver
}
private fun totaldelNotes(tvTotaldelv: TextView, tripsheetlist: Tripsheetlist) {
var totnotes :Int = tripsheetlist.videos.size
tvTotaldelv.setText("Total Delivery Notes: " +totnotes) }
private fun weightsum(tvTotalweight: TextView, tripsheetlist: Tripsheetlist) {
var totweight: Int = 0
var sum: Int = 0
for (i in 0 until tripsheetlist.videos.size) {
sum += tripsheetlist.videos[i].Weight }
totweight = sum
tvTotalweight.setText("Total Weight: " + totweight + "kg")
}
//not used
fun limitDropDownHeight(spinner: Spinner){
val popup = Spinner::class.java.getDeclaredField( "mPopup")
popup.isAccessible = true
val popupWindow = popup.get(spinner)as ListPopupWindow
popupWindow.height = (200 * resources.displayMetrics.density).toInt()
}
}
I have an application built using Jetpack Compose , where i also use paging library 3 to fetch data from db , i have multiple remote mediator where i fetch data and save it directly into database , the issue is that sometimes data gets saved , sometimes not , it goes to the point that sometimes one of the two only gets data stored.
Remote Mediator 1:
#ExperimentalPagingApi
class PopularClothingRemoteMediator #Inject constructor(
private val clothingApi: ClothingApi,
private val clothingDatabase: ClothingDatabase
) : RemoteMediator<Int, Clothing>(){
private val clothingDao = clothingDatabase.clothingDao()
private val clothingRemoteKeysDao = clothingDatabase.clothingRemoteKeysDao()
override suspend fun initialize(): InitializeAction {
val currentTime = System.currentTimeMillis()
val lastUpdated = clothingRemoteKeysDao.getRemoteKeys(clothingId = 1)?.lastUpdated ?: 0L
val cacheTimeout = 1440
val diffInMinutes = (currentTime - lastUpdated) / 1000 / 60
return if (diffInMinutes.toInt() <= cacheTimeout) {
// Log.d("RemoteMediator", "UP TO DATE")
InitializeAction.SKIP_INITIAL_REFRESH
} else {
// Log.d("RemoteMediator", "REFRESH")
InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
override suspend fun load(loadType: LoadType, state: PagingState<Int, Clothing>): MediatorResult {
return try {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextPage?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevPage = remoteKeys?.prevPage
?: return MediatorResult.Success(
endOfPaginationReached = remoteKeys != null
)
prevPage
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
val nextPage = remoteKeys?.nextPage
?: return MediatorResult.Success(
endOfPaginationReached = remoteKeys != null
)
nextPage
}
}
val response = clothingApi.getPopularClothing(page = page)
if (response.popularClothing.isNotEmpty()) {
clothingDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
clothingDao.deleteAllClothing()
clothingRemoteKeysDao.deleteAllRemoteKeys()
}
val prevPage = response.prevPage
val nextPage = response.nextPage
val keys = response.popularClothing.map { clothing ->
ClothingRemoteKeys(
clothingId = clothing.clothingId,
prevPage = prevPage,
nextPage = nextPage,
lastUpdated = response.lastUpdated
)
}
// When i debug this code , it works fine and the last line is executed
// the issue data sometimes gets saved , sometimes not
clothingRemoteKeysDao.addAllRemoteKeys(clothingRemoteKeys = keys)
clothingDao.addClothing(clothing = response.popularClothing)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextPage == null)
} catch (e: Exception) {
return MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.clothingId?.let { clothingId ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothingId)
}
}
}
private suspend fun getRemoteKeyForFirstItem(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
}
Remote Mediator 2:
class OuterwearRemoteMediator #Inject constructor(
private val clothingApi: ClothingApi,
private val clothingDatabase: ClothingDatabase
) : RemoteMediator<Int, Clothing>() {
private val clothingDao = clothingDatabase.clothingDao()
private val clothingRemoteKeysDao = clothingDatabase.clothingRemoteKeysDao()
override suspend fun initialize(): InitializeAction {
val currentTime = System.currentTimeMillis()
val lastUpdated = clothingRemoteKeysDao.getRemoteKeys(clothingId = 1)?.lastUpdated ?: 0L
val cacheTimeout = 1440
val diffInMinutes = (currentTime - lastUpdated) / 1000 / 60
return if (diffInMinutes.toInt() <= cacheTimeout) {
// Log.d("RemoteMediator", "UP TO DATE")
InitializeAction.SKIP_INITIAL_REFRESH
} else {
// Log.d("RemoteMediator", "REFRESH")
InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
override suspend fun load(loadType: LoadType, state: PagingState<Int, Clothing>): MediatorResult {
return try {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextPage?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevPage = remoteKeys?.prevPage
?: return MediatorResult.Success(
endOfPaginationReached = remoteKeys != null
)
prevPage
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
val nextPage = remoteKeys?.nextPage
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
nextPage
}
}
val response = clothingApi.getOuterwear(page = page)
if (response.outerwear.isNotEmpty()) {
clothingDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
clothingDao.deleteAllClothing()
clothingRemoteKeysDao.deleteAllRemoteKeys()
}
val prevPage = response.prevPage
val nextPage = response.nextPage
val keys = response.outerwear.map { clothing ->
ClothingRemoteKeys(
clothingId = clothing.clothingId,
prevPage = prevPage,
nextPage = nextPage,
lastUpdated = response.lastUpdated
)
}
// the same thing here
// When i debug this code , it works fine and the last line is executed
// the issue data sometimes gets saved , sometimes not
clothingRemoteKeysDao.addAllRemoteKeys(clothingRemoteKeys = keys)
clothingDao.addClothing(clothing = response.outerwear)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextPage == null)
} catch (e: Exception) {
return MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Clothing>): ClothingRemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.clothingId?.let { clothingId ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothingId)
}
}
}
private suspend fun getRemoteKeyForFirstItem(
state: PagingState<Int, Clothing>): ClothingRemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, Clothing>
): ClothingRemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { clothing ->
clothingRemoteKeysDao.getRemoteKeys(clothingId = clothing.clothingId)
}
}
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"
}
}
I need your help in the following problem: I can not get mocked the Fuel.get call.
What I've tried:
This is the service class, where Fuel will be called.
class JiraService {
private val logger: Logger = LoggerFactory.getLogger(JiraService::class.java)
fun checkIfUsersExistInJira(usernames: List<String>, jiraConfig: Configuration): Boolean {
var checkResultOk = true;
val encodedCredentials =
usernames.forEach {
Fuel.get("${jiraConfig[url]}/rest/api/2/groupuserpicker?query=${it}&maxResults=1")
.appendHeader("Authorization", "Basic ...")
.appendHeader("Content-Type", "application/json")
.responseObject(UserInfoDeserializer)
.third
.fold(
failure = { throwable ->
logger.error(
"Can't check users in jira for user $it",
throwable
)
},
success = { userExists ->
checkResultOk = checkResultOk && userExists
}
)
}
return checkResultOk
}
}
The test:
#ExtendWith(MockKExtension::class)
class JiraServiceTest {
private val jiraConfig = createJiraConf()
private val fuelRequest = mockk<Request>()
private val fuelResponse = mockk<Response>()
private val fuelMock = mockk<Fuel>()
#BeforeEach
fun setUp() {
mockkStatic(FuelManager::class)
every {
fuelMock.get(eq("${jiraConfig[url]}/rest/api/2/groupuserpicker?query=user_1#test.com&maxResults=1"))
.appendHeader(eq("Authorization"), any())
.appendHeader(eq("Content-Type"), eq("application/json"))
.responseObject(UserInfoDeserializer)
} returns ResponseResultOf(first = fuelRequest, second = fuelResponse, third = Result.success(false))
}
#Test
fun `will return FALSE for user not existing in jira`() {
// given
val usernames = listOf("user_1#test.com", "user_3#test.com", "user_4#test.com")
// when
JiraService().checkIfUsersExistInJira(usernames, jiraConfig)
// then
}
}
And I always see the error:
Caused by: some.url.net
com.github.kittinunf.fuel.core.FuelError$Companion.wrap(FuelError.kt:86)
com.github.kittinunf.fuel.toolbox.HttpClient.executeRequest(HttpClient.kt:39)
Caused by: java.net.UnknownHostException: some.url.net
...
So it always makes a real Fuel call ignoring fueMock.
What am I doing wrong?
Thanks for help!
If somebody need, following test works:
#ExtendWith(MockKExtension::class)
class JiraServiceTest {
private val jiraConfig = createJiraConf()
private val fuelRequest1 = mockk<DefaultRequest>()
private val fuelRequest3 = mockk<DefaultRequest>()
private val fuelRequest4 = mockk<DefaultRequest>()
private val fuelResponse = mockk<Response>()
#BeforeEach
fun setUp() {
mockkObject(Fuel)
every {
Fuel.get(eq("${jiraConfig[url]}/rest/api/2/groupuserpicker?query=user_1#test.com&maxResults=1"))
} returns fuelRequest1
every {
Fuel.get(eq("${jiraConfig[url]}/rest/api/2/groupuserpicker?query=user_3#test.com&maxResults=1"))
} returns fuelRequest3
every {
Fuel.get(eq("${jiraConfig[url]}/rest/api/2/groupuserpicker?query=user_4#test.com&maxResults=1"))
} returns fuelRequest4
every { fuelRequest1.appendHeader(any<String>(), any()) } returns fuelRequest1
every { fuelRequest3.appendHeader(any<String>(), any()) } returns fuelRequest3
every { fuelRequest4.appendHeader(any<String>(), any()) } returns fuelRequest4
every { fuelRequest1.responseObject(UserInfoDeserializer) } returns ResponseResultOf(
first = fuelRequest1,
second = fuelResponse,
third = Result.success(false)
)
every { fuelRequest3.responseObject(UserInfoDeserializer) } returns ResponseResultOf(
first = fuelRequest3,
second = fuelResponse,
third = Result.success(true)
)
every { fuelRequest4.responseObject(UserInfoDeserializer) } returns ResponseResultOf(
first = fuelRequest4,
second = fuelResponse,
third = Result.success(true)
)
}
#Test
fun `will return FALSE for user not existing in jira`() {
// given
val usernames = listOf("user_1#test.com", "user_3#test.com", "user_4#test.com")
// when
val result = JiraService().checkIfUsersExistInJira(usernames, jiraConfig)
// then
assertThat(result, equalTo(false))
}
#Test
fun `will return TRUE for users existing in jira`() {
// given
val usernames = listOf("user_3#test.com", "user_4#test.com")
// when
val result = JiraService().checkIfUsersExistInJira(usernames, jiraConfig)
// then
assertThat(result, equalTo(true))
}
}
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)
}
}
}
}
}