반응형

작업은 vscode 에서 진행한다.

만약 없다면 무료 툴 이므로 다운로드!

 

1. node js 설치

https://nodejs.org/en

 

 

2.  cordova 설치

cmd 창에서 진행하면 된다

$ npm install -g cordova

 

3. cordova 프로젝트 생성

내가원하는 폴더로 이동 후 아래 커맨드 실행

$ cordova create [프로젝트명] [패키지명] [프로젝트명]

ex)
$ cordova create cordova1 com.example.test cordova1

 

이렇게 프로젝트가 생성된다

 

처음에는 기본적으로 www 즉 웹만 생성되며 실행시켜볼수도있다.

 

vscode 플러그인으로 live server 를 추가하고 (구글링) 실행하면 코르도바 이미지가 보인다

 

 

 

4.  Android, iOS platform 추가

아래 커맨드로 안드로이드 소스코드를 추가할 수 있다

$ cordova platform add android

 

마찬가지로 ios로 추가해주면 아래와같이 platoforms에 android 와 ios가 생긴다

 

Android 같은 경우는, java 기반으로 소스코드가 생기는것을 확인해 볼 수 있다.

 

5. project build

 

이제 이것을 vscode에서 그대로 build 하려면 ios의 경우는 맥북이 필요하고

android 는 build 가능하나, 필요한 requirements들이 있다 (jdk 등등) 대신 안드로이드 스튜디오에서 해당프로젝트 실행해도됨

 

$ cordova requirements

 

나의 경우 위에서부터 하나씩 보면 

jdk : 17버전 있음

sdk  : 있음

android target : 없음

gradle : 없음

macOS : 없음

 

이렇게 확인된다

 

특히 android target 부분이나 밑에 gradle 을 보면 please install android studio 라는 부분을 볼수있는데 안드로이드 스튜디오에서 해당 프로젝트를 빌드하는게 속편할것같다

728x90
반응형

 

Health Connect API 가이드 문서가 있지만.. java 기준이기도하고, 실제로 적용해보니 이슈가 몇가지생겨서 적용하는방법을 정리해보고자 한다.

 

가이드문서 URL : https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started?hl=ko

 

헬스 커넥트 시작하기  |  Android health & fitness  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 헬스 커넥트 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에서는 앱에서 헬스

developer.android.com

 

시작하기에 앞서 안드로이드 버전을 확인하면 좋은데, 나는 안드로이드 13버전으로 테스트 해보았고

이 경우 health connect 앱이 설치되어있지 않았고 설치 후에도 삼성헬스데이터가 넘어오지 않았다.

 

헬스커넥트 메인페이지 최근 정보에 엑세스한 앱에도 뜨지않았고 데이터 및 액세스 탭에도 데이터가 아예 없었다.

 

이 경우 삼성헬스 앱 - 상단 점세개 -> 문의하기 에서 헬스커넥트로 데이터연동 안되어있다고 문의하면 하루..정도 걸려서 해주신다..ㅎㅎ..참고..

 

1. 라이브러리 임포트 및 권한 추가

 

우선 build.gradle 에 health connect client 를 import 해준다

    /*Health Connect*/
    implementation "androidx.health.connect:connect-client:1.1.0-alpha02"

 

그리고 manifest 에 내가 취득하고싶은 권한을 넣는다.

권한종류에 관해선 가이드 문서에 상세히 나와있으니 참고하도록 하자.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   ...
   
       <!--Health Connect Permissions-->
    <uses-permission android:name="android.permission.health.READ_STEPS"/>
    <uses-permission android:name="android.permission.health.READ_SLEEP"/>
    <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
    <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/>
    <uses-permission android:name="android.permission.health.READ_DISTANCE"/>
    <uses-permission android:name="android.permission.health.READ_EXERCISE"/>
    <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED"/>

    <!--Health Connect-->
    <queries>
        <package android:name="com.google.android.apps.healthdata" />
    </queries>

    ...
    
</manifest>

 

내가 원하는 권한 종류들을 추가하였다

 

2. Health Connect API 호출

 

해당 API 를 호출하기 원하는 activity 혹은 fragment에서 호출하면 된다.

 

class MainFragment : Fragment() {

    private var TAG : String = "MAIN FRAGMENT"
    private var _binding : FragmentMainBinding? = null
    private val binding : FragmentMainBinding
        get() = _binding!!

    private lateinit var healthConnectClient: HealthConnectClient
    
    //manifest 에서 선언한 권한과 동일해야함
    private val permissionList = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getReadPermission(SleepSessionRecord::class),
        HealthPermission.getReadPermission(ActiveCaloriesBurnedRecord::class),
        HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
        HealthPermission.getReadPermission(DistanceRecord::class),
        HealthPermission.getReadPermission(ExerciseSessionRecord::class),
        HealthPermission.getReadPermission(FloorsClimbedRecord::class)
    )

    private val requestPermissions = registerForActivityResult(
        PermissionController.createRequestPermissionResultContract()
    ) { granted ->
        if (granted.containsAll(permissionList)) {
            Log.d("MAIN ACTIVITY", "requestPermissions success ==== ")
            CoroutineScope(Dispatchers.IO).launch {
                readStepsData()
            }
        } else {
            CustomToast.createToast(requireContext(), "건강정보 가져오기에 실패하였습니다.\n권한을 추가하거나 헬스 커넥트 앱을 다운로드 해주세요.")?.show()
            Log.d("MAIN ACTIVITY", "requestPermissions fail ////////////////// ")
            activity?.finish()
            openPlayStoreForHealthConnect()
        }
    }

    companion object {
        fun newInstance(): MainFragment {
            val fragment = MainFragment()
            val args = Bundle()
            fragment.arguments = args
            return fragment
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding =
            DataBindingUtil.inflate(layoutInflater, R.layout.fragment_main, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Health Connect
        CoroutineScope(Dispatchers.Main).launch {
            connectHealthData()
        }

    }

    private fun connectHealthData() {
        // 1. Health Connect 앱 유무 확인
        val availabilityStatus = HealthConnectClient.getSdkStatus(requireContext(), Constants.HEALTH_CONNECT_PACKAGE_NAME)
        if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
            CustomToast.createToast(requireContext(), "헬스 커넥트 앱을 다운로드 해주세요")?.show()
            activity?.finish()
            openPlayStoreForHealthConnect()
        }

        // 2. Health connect sdk update 확인
        if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
            CustomToast.createToast(requireContext(), "헬스 커넥트 업데이트가 필요합니다")?.show()
            activity?.finish()
            openPlayStoreForHealthConnect()
        }

        // 4. Check permissions and run
        healthConnectClient = HealthConnectClient.getOrCreate(requireContext())
        requestPermissions.launch(permissionList)
    }


    private suspend fun readStepsData() {
        val now: LocalDateTime = LocalDateTime.now()
        val startOfDay = LocalDateTime.of(now.toLocalDate(), LocalTime.MIDNIGHT)

        val request = ReadRecordsRequest(
            recordType = StepsRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startOfDay, now),
        )

        val response = healthConnectClient.readRecords(request)
        val steps = response.records.sumOf { it.count }

        //토탈 칼로리
        val totalCaloriesRequest = ReadRecordsRequest(
            recordType = TotalCaloriesBurnedRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startOfDay, now),
        )
        val totalCaloriesResponse = healthConnectClient.readRecords(totalCaloriesRequest)
        val totalCalories = totalCaloriesResponse.records.sumOf { it.energy.inCalories }

        //활동 칼로리
        val activeCaloriesRequest = ReadRecordsRequest(
            recordType = ActiveCaloriesBurnedRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startOfDay, now),
        )
        val activeCaloriesResponse = healthConnectClient.readRecords(activeCaloriesRequest)
        val activeCalories = activeCaloriesResponse.records.sumOf { it.energy.inCalories }

        CoroutineScope(Dispatchers.Main).launch {
            binding.tvStepCount.text = steps.toString()
            binding.tvTotalCaloriesCount.text = totalCalories.toString()
            binding.tvActiveCaloriesCount.text = activeCalories.toString()
        }

    }

    private fun openPlayStoreForHealthConnect() {
        val intent = Intent(Intent.ACTION_VIEW).apply {
            data = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata")
            setPackage("com.android.vending")
        }
        startActivity(intent)
    }
}

 

삼성헬스 데이터가 health connect 에 연결되었는가?

health connect 버전이 최신인가?

health connect 가 설치되어있는가?

 

등등 우선시 되어야 할 상황들을 체크하는 로직도 추가하였다.

 

3. Permission Activity 추가

 

이상하게 이걸 추가안하면..연결 계속 fail 뜬다.

아직 파악이 덜됨..

 

1) PermissionActivity.kt 추가

@AndroidEntryPoint
class PermissionActivity : AppCompatActivity(){
    private lateinit var binding: ActivityPermissionBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityPermissionBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val permissions = setOf(
            HealthPermissions.READ_STEPS
        )

        val permissionController = HealthConnectClient.getOrCreate(this).permissionController
        //permissionController.pe(permissions, this)

    }

    private val permissionRequest = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) {
        // 권한 요청 후의 결과 처리
        // 필요한 경우 MainActivity로 돌아가게 함
        startActivity(Intent(this, MainActivity::class.java))
    }

 

 

2) layout 추가

<?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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".PermissionActivity">

        <TextView
            android:text="test - permission"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/nav_area"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:menu="@menu/navigation_menu"
            app:itemIconTint="@color/black"
            app:labelVisibilityMode="labeled"
            app:itemTextColor="@android:color/transparent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

3) Manifest 수정

  ....
  
  <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".ui.permission.PermissionActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>
        </activity>
        ...

 

해당코드는 깃헙에 올려두었다.

 

그런데, PermissionActivity 추가하는 로직에 관해선, 공식문서에 나와잇지만 안드로이드12이상부터는 아래와같이써야한다

 <activity
            android:name=".ui.permission.PermissionActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>
        </activity>

        <activity-alias
            android:name="ViewPermissionUsageActivity"
            android:exported="true"
            android:targetActivity=".ui.permission.PermissionActivity"
            android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
            <intent-filter>
                <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
                <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
            </intent-filter>
        </activity-alias>

 

아래 링크의 Fitness 프로젝트!

https://github.com/Daseul727/Mobile-Skill-Up

 

+

2024.10.29

하지만 결국..데이터 잘 안뽑혀서 삼성헬스 -> 점세개 -> 설정 -> 고객센터 -> 1:1문의에서 헬스커넥터로 데이터 안들어온다고 캡쳐화면이랑 문의글 올리면...들어오게 처리해준다 ㅠ 답변은 따로없다..다음날이나 몇시간 후 확인해보면됨..

728x90
반응형

 

Kotlin Native Android App 개발 중 걸음수가 필요해서 Samsung Health API 를 달려고 했는데

결론적으로는 Samsung partnership 등록이 필요했고, 현재로서는 더이상 받고있지않는다는 답변을 받았다.

질문 올렸던 URL : https://forum.developer.samsung.com/t/samsung-health-api-is-not-working-for-android-device/33200/2

 

하지만 작업한게 아까워서 기록용으로 남기려고한다.

 

 

 

이 모든것은 작동안되는 방법이므로 health connect 앱으로 작업하세요!!

 

 

 

1. SDK 다운로드

 

SDK 추가방법은 build.gradle 에 추가하거나 직접 파일추가하는 방법이 있다.

build.gradle 에 추가했을때 import가 되지 않아서 나는 아래 링크의 예제 프로젝트에서 받았다

(simplehealth\app\src\main\libs\samsung-health-data-1.5.0.aar)

https://developer.samsung.com/health/android/sample/simple-health.html

 

Data | SimpleHeath | Samsung Developer

The world runs on you.

developer.samsung.com

 

그리고 내 프로젝트의 libs에 넣고 build.gradle에 file 위치를 알려줬다.

    /*Samsung sdk*/
    implementation files('libs/samsung-health-data-1.5.0.aar')

 

2. AndroidManifest 수정


<manifest>

...
    <uses-permission android:name="android.permission.INTERNET"/>

    <uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <!--삼성 SDK-->
    <queries>
        <package android:name="com.sec.android.app.shealth" />
    </queries>

    <application

        ....
    >

        <!--삼성 SDK 권한-->
        <meta-data android:name="com.samsung.android.health.permission.read"
            android:value="com.samsung.health.step_count;com.samsung.shealth.step_daily_trend;com.samsung.health.exercise"/>

        <activity
            android:name=".ui.main.MainActivity"
            ...
        />

	</application>

</manifest>

 

마지막 삼성 SDK 권한쪽 보면 value에 원하는 데이터를 콤마의 형태로 여러번 불러올수있다.

나는 읽기만 필요하니까 read 만 호출한것.

 

참고 가이드 URL : https://developer.samsung.com/health/android/data/api-reference/com/samsung/android/sdk/healthdata/HealthPermissionManager.html

 

 

3. Samsnung Health API 연동

 

1) initDataStore 메소드에서 HealthDataStore 를 초기화시키고 실행

2) onConnected 에서 필요한 권한을 세팅하고 (이때 Manifest에서 설정한 권한과 동일해야함) 권한 request

3) 그러면 권한 입력하는 팝업이 호출됨 

 

4) 권한 선택 여부가 mPermisionListener 로 들어옴

...

import com.samsung.android.sdk.healthdata.HealthConnectionErrorResult
import com.samsung.android.sdk.healthdata.HealthConstants
import com.samsung.android.sdk.healthdata.HealthConstants.Exercise
import com.samsung.android.sdk.healthdata.HealthConstants.StepDailyTrend
import com.samsung.android.sdk.healthdata.HealthDataResolver
import com.samsung.android.sdk.healthdata.HealthDataStore
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
import com.samsung.android.sdk.healthdata.HealthPermissionManager
import com.samsung.android.sdk.healthdata.HealthPermissionManager.PermissionKey
import com.samsung.android.sdk.healthdata.HealthPermissionManager.PermissionResult
import com.samsung.android.sdk.healthdata.HealthPermissionManager.PermissionType
import com.samsung.android.sdk.healthdata.HealthResultHolder


@AndroidEntryPoint
class HomeFragment : Fragment() {

    private var TAG : String = "HOME FRAGMENT"
    private lateinit var mStore: HealthDataStore
    private lateinit var mKeySet: MutableSet<PermissionKey>

	...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        //삼성 SDK 초기화
        initDataStore()
    }


    /**
     * 삼성 SDK 초기화
     */
    private fun initDataStore() {
    	//activity 에서 실행한다면 requireContext 대신 this
        mStore = HealthDataStore(requireContext(), mCntListener) 
        mStore.connectService()
    }

    /**
     * 삼성 SDK Listener 설정
     */
    private val mCntListener: ConnectionListener = object : ConnectionListener {
        override fun onConnected() {
            Log.d(TAG, "Health data service is connected.")

            val mManager = HealthPermissionManager(mStore)

            //내가 원하는 권한요청
            mKeySet = HashSet()
            mKeySet.add(PermissionKey(HealthConstants.StepCount.HEALTH_DATA_TYPE, PermissionType.READ))
            mKeySet.add(PermissionKey(StepDailyTrend.HEALTH_DATA_TYPE, PermissionType.READ))

            val resultMap : MutableMap<PermissionKey, Boolean> = mManager.isPermissionAcquired(mKeySet)

            if(resultMap.containsValue(false)) { //만약 권한이 없다면 권한요청 UI 팝업
                mManager.requestPermissions(mKeySet, requireActivity()).setResultListener(mPermissionListener)
            } else {
                binding.tvStepCountText.text = getStepCount().toString()
            }
        }

        override fun onConnectionFailed(error: HealthConnectionErrorResult) {
			//연결 실패 시 실행할 로직
        }

        override fun onDisconnected() {
            mStore.disconnectService() // 연결종료
        }
    }

    private val mPermissionListener = HealthResultHolder.ResultListener<PermissionResult> { result ->
        Log.d(TAG, "Permission callback is received.")
        val resultMap = result.resultMap

        if (resultMap.containsValue(false)) {
            Log.d(TAG, "Permission false")
        } else {
            binding.tvStepCountText.text = getStepCount().toString()
        }
    }
    
    private fun getStepCount() : Int{
    var stepCount = 0;

    val resolver = HealthDataResolver(mStore, null)
    val request = HealthDataResolver.ReadRequest.Builder()
        .setDataType(HwConstants.DATA_TYPE_STEP_COUNT)
        .build()

    resolver.read(request).setResultListener { result ->
        val iterator = result.iterator()
        while (iterator.hasNext()) {
            val data = iterator.next()
            stepCount = data.getInt("count")
        	}
        	result.close()
    	}
    	return stepCount
	}
}

 

 

나의 경우 3번을 진행하다가 아무리해도 권한체크하는 영역이 노출되지않아서 문의를 했었다.

 

모든권한 하단에 내가 요청한 권한목록이 떴어야 했는데... 

 

결론적으로는 해당 API 는 삼성 파트너쉽 앱 등록 후 사용하여야하는데 현재 서비스 리뉴얼중으로 (몇달간 계속되는듯..) 사용불가.

 

걸음수 가져가고싶으면 헬스 커넥트 앱을 사용하라는 답변!

후... 이거 다 지우고 헬스 커넥트 앱으로 재도전 ^^....

 

728x90
반응형

안드로이드 스튜디오 사용중에 update 가 필요해서 진행하고 다시 켰더니

 

the environment variable java_home does not point to a valid jvm installation androidstudio

 

이유를 찾아보면 JAVA_HOME URL 변경하라고 나오는데 나는 그 케이스도 아니었다.

 

update 말고 그냥 삭제 후 재설치 하니까 잘됨;;

update할때 뭔가 꼬인듯

728x90
반응형

 

TextView 등 기타 요소를 사용하여 키보드를 활성화시키면 키보드의 화살표를 눌러야 키보드가 숨겨진다

 

하지만 보통 사용자가 사용할대는 화면을 클릭했을때 없어지길 바라는데,

Activity 에 아래의 로직을 선언하면 키보드는 감춰진다

 

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    val imm: InputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0)

    return super.dispatchTouchEvent(ev)
}

 

728x90

+ Recent posts