Building a Number Prediction and followup widget
Minimum SDK Version
This is a guide on building a custom Number Prediction and followup Widget. For an overview of the Custom Widget UI system see Custom Widget UI.
Number Prediction Widget Model
The Number Prediction Widget Model is responsible for providing prediction specific data and remote apis to submit prediction.
Number Prediction Data(object of LiveLikeWidget class)
The Number Prediction Data provides data about the Number Prediction Widget such as the question and the options consisting of imageUrl and description.
The model also provides metadata about the widget such as the Date that it was created or the timeout duration set by the Producer.
Note: Use options in LiveLikeWidget class for the Number Prediction.
Example show below:-
widgetData?.let { liveLikeWidget ->
liveLikeWidget.options?.let { option ->
if (option.size > 2) {
binding.rcylPredictionList.layoutManager =
GridLayoutManager(
context,
2
)
}
val adapter =
PredictionListAdapter(
context,
isImage,
ArrayList(option.map { item -> item!! })
)
binding.rcylPredictionList.adapter = adapter
}
}
lockInVote
For submitting the predictions you need to call lockInVote(options:List), with list of NumberPredictionVotes (consisting of the optionId and the number). It is mandatory to submit the prediction for all the options.
numberPredictionWidgetViewModel?.lockInVote(optionList)
Interaction History
To load the interaction history, you can call the loadInteractionHistory method
Example:-:
numberPredictionWidgetViewModel?.loadInteractionHistory(object :
LiveLikeCallback<List<NumberPredictionWidgetUserInteraction>>() {
override fun onResponse(
result: List<NumberPredictionWidgetUserInteraction>?,
error: String?
) {
if (!result.isNullOrEmpty()) {
// interaction results
}
}
})
Number Prediction FollowUp Widget Model
The follow-up model has the same functions as the above one except lockInVote.
getPredictionVotes()
This method on the model allows you to retrieve the predicted vote list, on which user voted for the prediction associated with this follow up
val votedList = followUpWidgetViewModel?.getPredictionVotes()
claimRewards()
claims the rewards on the number prediction follow up using a user’s prediction
returns nothing but notifies leaderboard clients
followUpWidgetViewModel?.claimRewards()
Full Number prediction sample with followup
class CustomNumberPredictionWidget :
ConstraintLayout {
var numberPredictionWidgetViewModel: NumberPredictionWidgetModel? = null
var followUpWidgetViewModel: NumberPredictionFollowUpWidgetModel? = null
private lateinit var binding: CustomNumberPredictionWidgetBinding
var isImage = false
var isFollowUp = false
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
context,
attrs,
defStyle
) {
init()
}
private fun init() {
binding = CustomNumberPredictionWidgetBinding.inflate(
LayoutInflater.from(context),
this@CustomNumberPredictionWidget,
true
)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
var widgetData = numberPredictionWidgetViewModel?.widgetData
if (isFollowUp) {
widgetData = followUpWidgetViewModel?.widgetData
}
widgetData?.let { liveLikeWidget ->
liveLikeWidget.options?.let { option ->
if (option.size > 2) {
binding.rcylPredictionList.layoutManager =
GridLayoutManager(
context,
2
)
}
val adapter =
PredictionListAdapter(
context,
isImage,
ArrayList(option.map { item -> item!! })
)
binding.rcylPredictionList.adapter = adapter
binding.txt.text = liveLikeWidget.question
getInteractedData(adapter) // get user interaction
setOnClickListeners(adapter)
if (isFollowUp) {
binding.btn1.visibility = View.GONE
claim_rewards.visibility = View.VISIBLE
} else {
binding.btn1.visibility = View.VISIBLE
claim_rewards.visibility = View.GONE
}
if (isFollowUp) {
val votedList = followUpWidgetViewModel?.getPredictionVotes()
votedList?.forEach { op ->
adapter.predictionMap[op?.optionId!!] = op.number ?: 0
}
adapter.isFollowUp = true
} else {
result_tv.visibility = GONE
}
}
}
}
private fun setOnClickListeners(adapter: PredictionListAdapter) {
// predict button click
binding.btn1.setOnClickListener {
if (!isFollowUp) {
val optionList = submitVoteRequest(adapter)
numberPredictionWidgetViewModel?.lockInVote(optionList)
}
}
binding.imgClose.setOnClickListener {
finish()
}
claim_rewards.setOnClickListener{
followUpWidgetViewModel?.claimRewards()
}
}
private fun submitVoteRequest(adapter: PredictionListAdapter):List<NumberPredictionVotes> {
val optionList = mutableListOf<NumberPredictionVotes>()
val maps = adapter.getPredictedScore()
if(maps.isNullOrEmpty()){
val options = numberPredictionWidgetViewModel?.widgetData?.options
for(item in options!!){
optionList.add(
NumberPredictionVotes(
optionId = item?.id!!,
number = 0
)
)
}
}
return optionList
}
//get user interacted data from load history api
private fun getInteractedData(adapter: PredictionListAdapter) {
val lists = numberPredictionWidgetViewModel?.getUserInteraction()
lists?.votes?.let { scores ->
adapter.setInteractedData(scores)
adapter.notifyDataSetChanged()
}
}
fun finish() {
numberPredictionWidgetViewModel?.finish()
followUpWidgetViewModel?.finish()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// numberPredictionWidgetViewModel?.voteResults?.unsubscribe(this)
}
// adapter
class PredictionListAdapter(
private val context: Context,
private val isImage: Boolean,
val list: ArrayList<OptionsItem>
) : RecyclerView.Adapter<PredictionListAdapter.PredictionListItemViewHolder>() {
var predictionMap: HashMap<String, Int> = HashMap()
var isFollowUp = false
fun getPredictedScore(): HashMap<String, Int> {
return predictionMap
}
class PredictionListItemViewHolder(view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): PredictionListItemViewHolder {
return PredictionListItemViewHolder(
LayoutInflater.from(parent.context!!).inflate(
R.layout.custom_number_prediction_item,
parent, false
)
)
}
override fun onBindViewHolder(
holder: PredictionListItemViewHolder,
position: Int
) {
val item = list[position]
if (isImage) {
Glide.with(context)
.load(item.imageUrl)
.into(
holder.itemView.img_1
)
holder.itemView.text_1.visibility = View.GONE
holder.itemView.img_1.visibility = View.VISIBLE
} else {
holder.itemView.text_1.text = item.description
holder.itemView.text_1.visibility = View.VISIBLE
holder.itemView.img_1.visibility = View.GONE
}
if (isFollowUp) {
holder.itemView.option_view_1.text = "${predictionMap[item.id!!] ?: 0}"
holder.itemView.correct_op.text = item.correctNumber.toString()
holder.itemView.correct_op.visibility = View.VISIBLE
}else{
holder.itemView.correct_op.visibility = View.GONE
}
if (!isFollowUp) {
if (item.number != null) {
holder.itemView.option_view_1.text = item.number.toString()
} else {
holder.itemView.option_view_1.text = "0"
}
}
holder.itemView.plus.setOnClickListener {
if (!isFollowUp) {
val updatedScore = holder.itemView.option_view_1.text.toString().toInt() + 1
holder.itemView.option_view_1.text = updatedScore.toString()
predictionMap[item.id!!] = updatedScore
}
}
holder.itemView.minus.setOnClickListener {
if (!isFollowUp) {
val updatedScore = holder.itemView.option_view_1.text.toString().toInt() - 1
holder.itemView.option_view_1.text = updatedScore.toString()
predictionMap[item.id!!] = updatedScore
}
}
}
override fun getItemCount(): Int = list.size
fun setInteractedData(interactedList: List<NumberPredictionVotes>) {
for (i in list.indices) {
if (list[i].id == interactedList[i].optionId) list[i].number =
interactedList[i].number
}
}
}
}
Updated 11 months ago