Архив за день: 28.06.2022



Android Studio: Создание простого шагомера (Step Counter) на Kotlin (часть 2)

Продолжим создание шагомера.
В одной из прошлых заметок я описал процесс работы с разрешениями, там же был приведён код файла дизайна activity_main.xml. В данном случае нам требовался доступ к датчику активности (шагомеру). Мы научились запрашивать доступ, но теперь нам нужно использовать этот датчик, поскольку приложение получило разрешение на доступ к сенсору.

За основу я взял старый туториал, в нём не описан процесс запроса доступа разрешений, но в целом всё объясняется достаточно грамотно:

Поскольку мы задались целью создать «простой» шагомер, то я не стал включать в свой код лишние библиотеки для отрисовки круговой шкалы прогресса. Ниже я сразу приведу окончательный код. На этот раз он должен работать на Android 10+.

MainActivity.kt
package com.example.simplestepcounter_kotlin

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.simplestepcounter_kotlin.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), SensorEventListener
{
    private lateinit var binding: ActivityMainBinding

    private var sensorManager: SensorManager? = null

    private var running = false

    private var totalSteps = 0f
    private var previousTotalSteps = 0f

    override fun onCreate(savedInstanceState: Bundle?)
    {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION) == PackageManager.PERMISSION_GRANTED)
        {
            onStepCounterPermissionGranted()
        } else
        {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACTIVITY_RECOGNITION), RQ_PERMISSION_FOR_STEPCOUNTER_CODE)
        }

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)

        loadData()
        resetSteps()

        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

        val view = binding.root
        setContentView(view)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray)
    {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode)
        {
            RQ_PERMISSION_FOR_STEPCOUNTER_CODE ->
            {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
                {
                    onStepCounterPermissionGranted()
                } else
                {
                    if (!shouldShowRequestPermissionRationale(Manifest.permission.ACTIVITY_RECOGNITION))
                    {
                        askUserForOpeningUpSettings()
                    }
                }
            }
        }
    }

    private fun askUserForOpeningUpSettings()
    {
        val sppSettingsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null))
        if (packageManager.resolveActivity(sppSettingsIntent, PackageManager.MATCH_DEFAULT_ONLY) == null)
        {
            Toast.makeText(this, "Permissions are denied forever", Toast.LENGTH_SHORT).show()
        } else
        {
            AlertDialog.Builder(this)
                .setTitle("Permission denied")
                .setMessage(
                    "You have denied permissions permanently. " +
                            "You can change it in app settings.\n\n" +
                            "Would you like to open app settings?"
                )
                .setPositiveButton("Open") { _, _ ->
                    startActivity(sppSettingsIntent)
                }
                .create()
                .show()
        }
    }

    private fun onStepCounterPermissionGranted()
    {
        Toast.makeText(this, "StepCounter sensor permission is granted", Toast.LENGTH_LONG).show()
    }

    private companion object
    {
        const val RQ_PERMISSION_FOR_STEPCOUNTER_CODE = 1
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int)
    {
//        Not implemented yst
    }

    override fun onSensorChanged(event: SensorEvent?)
    {
        if (running)
        {
            totalSteps = event!!.values[0]
            val currentSteps = totalSteps.toInt() - previousTotalSteps.toInt()
            binding.textViewSteps.text = ("$currentSteps")
        }
    }

    override fun onResume()
    {
        super.onResume()
        running = true
        val stepSensor: Sensor? = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)

        if (stepSensor == null)
        {
            Toast.makeText(this, "No Step sensor detected on this device", Toast.LENGTH_SHORT).show()
        } else
        {
            sensorManager?.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI)
        }
    }

    private fun resetSteps()
    {
        binding.textViewSteps.setOnClickListener {
            Toast.makeText(this, "Long tap to reset steps", Toast.LENGTH_SHORT).show()
        }
        binding.textViewSteps.setOnLongClickListener {
            previousTotalSteps = totalSteps
            binding.textViewSteps.text = 0.toString()
            saveData()
            true
        }
    }

    private fun saveData()
    {
        val sharedPreferences = getSharedPreferences( "myPrefs", Context.MODE_PRIVATE)
        val editor = sharedPreferences.edit()
        editor.putFloat("key1", previousTotalSteps)
        editor.apply()
    }

    private fun loadData()
    {
        val sharedPreferences = getSharedPreferences( "myPrefs", Context.MODE_PRIVATE)
        val savedNumber = sharedPreferences.getFloat("key1", 0f)
        Log.d("MainActivity", "$savedNumber")
        previousTotalSteps = savedNumber
    }
}

Напоминаю, что это «простой» шагомер. Поэтому в нём мы отображаем и подсчитываем только шаги, которые совершили сразу после запуска приложения. Т.о. сначала мы получаем данные с датчика, затем обнуляем внутренний счётчик приложения. В противном случае приложение отображало бы общее количество шагов совершённых после перезагрузки устройства. В качестве небольшого дополнения мы добавили обработку события долгого нажатия на число количества шагов — в этом случае счётчик сбросится на 0 (функция resetSteps). В коде приложения для большей наглядности и удобства используется механизм привязки.