custom ContentProvider 만들기-3 (다른 앱을 통한 제어)
Custom ContentPrivider 만들기 -2 편에 연속됩니다.
기존 동일한 앱에 contentProvider , SQLiteOpenHelper ,MainActivity 를 모두 가지고 테스트 했습니다.
이제 다른앱을 통한 제어를 해보겠습니다. (MainaActivity 부분만 다른 앱으로 옮기도록 하겠습니다.)
1> 다른앱에서 Uri 및 table name , column name 를 쉽게 접근하기 위한 Contract class를 한개 만들겠습니다.
코틀린에서는 object 로 추가해야 합니다.
object MyContract {
const val AUTHORITY ="com.leevisual.mykotlin.PROVIDER"
val BASE_CONTENT_URI = Uri.parse("content://" + AUTHORITY)
object Entry : BaseColumns {
val TABLE_NAME = "ENGLISH"
val COLUMN_NAME = "NAME"
val COLUMN_SCORE = "SCORE"
}
}
"Custom ContentPrivider 만들기 -2 " 에 적용한 MainActivity 를 그대로 위의 MyContract 버전으로 수정.
class MainActivity : AppCompatActivity() {
val TAG = MainActivity::class.java.simpleName
object MyContract {
const val AUTHORITY ="com.leevisual.mykotlin.PROVIDER"
val BASE_CONTENT_URI = Uri.parse("content://" + AUTHORITY)
object Entry : BaseColumns {
val TABLE_NAME = "ENGLISH"
val COLUMN_NAME = "NAME"
val COLUMN_SCORE = "SCORE"
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btnQuery = findViewById<Button>(R.id.btn_query)
val btnInsert = findViewById<Button>(R.id.btn_insert)
val btnShowAll = findViewById<Button>(R.id.show_all)
val btnDelete = findViewById<Button>(R.id.delete_column)
val btnDeleteAll = findViewById<Button>(R.id.delete_all)
val editName = findViewById<EditText>(R.id.edit_name)
val editScore = findViewById<EditText>(R.id.edit_score)
val logMsg = findViewById<TextView>(R.id.log_text)
btnShowAll.setOnClickListener {
Log.d(TAG,"Show all columns ")
val cursor = contentResolver.query(MyContract.BASE_CONTENT_URI,null, null,null,null)
// show all data.
with(cursor!!) {
if (count > 0){
logMsg.append("Show data all. \n")
while (moveToNext()) {
val id = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val score = getInt(getColumnIndexOrThrow(MyContract.Entry.COLUMN_SCORE))
val name = getString(getColumnIndexOrThrow(MyContract.Entry.COLUMN_NAME))
Log.d(TAG, "query name = $name, score = $score")
logMsg.append("query id =$id name = $name, score = $score \n")
}
}else{
Log.d(TAG, "Data is not existed.")
}
}
Log.d(TAG,"contentProvider - ")
}
btnQuery.setOnClickListener {
Log.d(TAG,"test contentProvider + ")
// check name
if(editName.text.isEmpty()){
}else {
// show data which is matched by editName.
val selection = "${MyContract.Entry.COLUMN_NAME} = ?"
val selectionArgs = arrayOf("${editName.text.toString()}")
val cursor = contentResolver.query(MyContract.BASE_CONTENT_URI, null, selection, selectionArgs, null)
with(cursor!!) {
if (count > 0) {
logMsg.append("Show data about ${editName.text}. \n")
while (moveToNext()) {
val id = cursor.getInt(getColumnIndexOrThrow(BaseColumns._ID))
val score = this.getInt(getColumnIndexOrThrow(MyContract.Entry.COLUMN_SCORE))
val name = getString(getColumnIndexOrThrow(MyContract.Entry.COLUMN_NAME))
editScore.setText(score.toString())
logMsg.append("query id =$id name = $name, score = $score \n")
}
} else {
logMsg.append("Data is not existed. \n")
}
}
}
Log.d(TAG,"contentProvider - ")
}
btnInsert.setOnClickListener {
Log.d(TAG,"insert database")
val name = editName.text
val score = editScore.text
if(name.isEmpty() || score.isEmpty()){
Log.d(TAG,"name's or score's value is not defined.")
Toast.makeText(applicationContext,"name's or score's value is not defined.",Toast.LENGTH_SHORT).show()
}else {
// Create a new map of values , where column names are the keys
val values = ContentValues().apply {
put(MyContract.Entry.COLUMN_NAME, name.toString())
put(MyContract.Entry.COLUMN_SCORE, score.toString().toInt())
}
val uri =contentResolver.insert(MyContract.BASE_CONTENT_URI,values)
Log.d(TAG,"uri = $uri")
// logMsg.append("Insert id =$newRowId " + name.toString() + " " + score.toString() + "\n")
}
}
btnDelete.setOnClickListener {
var strName = editName.text
val projection = arrayOf(BaseColumns._ID, MyContract.Entry.COLUMN_NAME, MyContract.Entry.COLUMN_SCORE )
val selection = "${MyContract.Entry.COLUMN_NAME} = ?"
val selectionArgs = arrayOf("${strName.toString()}")
// find id from COLUMN_NAME_TITLE
val cursor = contentResolver.query(MyContract.BASE_CONTENT_URI,projection,selection,selectionArgs,null)
with(cursor!!) {
if(cursor.count > 0) {
while (moveToNext()) {
// delete column to use _id
val id = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
Log.d(TAG, "id = $id")
contentResolver.delete( MyContract.BASE_CONTENT_URI,"_id=$id",null)
logMsg.append("Delete column of id =$id \n")
}
}else {
Log.d(TAG, "$strName is not existed.")
logMsg.append("Failed to find " + strName.toString() +". \n")
}
}
}
btnDeleteAll.setOnClickListener {
contentResolver.delete(MyContract.BASE_CONTENT_URI, null,null)
logMsg.append("Delete all items. \n")
}
}
}
위코드 설명은 "custom ContentProvider 만들기-2" 참고하세요.
하여간 이게 동작을 하네요. Permission 수정도 안했는데..
기존 myKotlin 의 AndroidManifest.xml provider tag 에 다음이 있어서 동작했네요.
<provider
android:name=".MyContentProvider"
android:authorities="com.leevisual.mykotlin.PROVIDER"
android:enabled="true"
android:exported="true"></provider>
==> exported = "false" 를 넣으니 역시나 동작을 안하네요. 물론 동일 앱에서는 동작합니다.
myKotlin with custom provider , myKotlin2 라는 두개의 앱이 있다고 할때.. myKotlin 앱만
contentResolver 를 사용해 access 가능합니다.
exported = true 면 myKotlin2 앱도 access 가능합니다.
1> 2가지 앱 소스의 AndroidManifest.xml 에 수정사항이 발생하네요.
1-1> custom provider 포함앱 수정사항
<permission android:name="com.leevisual.mykotlin.PROVIDER.permission.READ"
android:label="com.xxx.mykotlin custom Provider read"
android:protectionLevel="normal"/>
<permission android:name="com.leevisual.mykotlin.PROVIDER.permission.WRITE"
android:label="com.xxx.mykotlin custom Provider write"
android:protectionLevel="normal"/>
........중략
<application
........중략
<provider
android:name=".MyContentProvider"
android:authorities="com.leevisual.mykotlin.PROVIDER"
android:enabled="true"
android:readPermission="com.leevisual.mykotlin.PROVIDER.permission.READ"
android:writePermission="com.leevisual.mykotlin.PROVIDER.permission.WRITE"
android:exported="true"></provider>
</application>
permission tag 부분과 provider tag의 readPermission, writePermission 추가했습니다.
1-2> contentResolver를 통한 custom provider 사용앱 수정사항
<uses-permission android:name="com.leevisual.mykotlin.PROVIDER.permission.READ"/>
<uses-permission android:name="com.leevisual.mykotlin.PROVIDER.permission.WRITE"/>
이후 정상 동작하네요.
< 정리>
custom provider 쪽에서 read/write permission 추가하기
other app 에서 uses-permission tag를 통한 read/write 추가하기