我是 Kotlin 新手,我正在制作一个小应用程序,用户可以在其中逐一插入他的联系人以及每个联系人的更多详细信息。 插入全部或大部分联系人后,他将能够将数据导出到 .txt 文件中,txt 文件的每一行将是每个联系人的详细信息(以非常特定的格式,每个字段将用“;”)。
退出应用程序后,用户将能够插入相同的文件,并且他的所有联系人将被重新插入到数据库(SQLite)中,以便他可以从中断的地方继续。
目前我已经实现了应用程序的大部分实用程序(将联系人插入 SQLite、获取联系人并使用 recyclerView 显示它们等),剩下的就是通过 txt 文件导出和重新插入数据。
我已经做了一个意图,以便我可以打开android的本机目录选择器,在用户想要的位置创建一个文件,然后写入该文件,但是执行后,该文件是空的。 我想将文件保存在外部(下载文件夹),以便用户能够自己打开它(通过文本文件阅读器或通过我已经制作的其他应用程序之一 - 主要是桌面应用程序)
我已经测试过字符串生成器是否有效,并且他输出的几乎正是我想要的(理想情况下,我希望从最终结果的开头和结尾删除方括号,并将每行联系人放在新行中,并且用“/n”分隔每一行,而不是用“,”)
这是我的数据类对象:
package com.example.contactsform.model
data class ContactModelClass(
val id: Int,
val name: String,
val surname: String,
val dateOfBirth: String,
val phoneNumber: String,
val email: String,
val address: String,
val city: String,
val notes: String)
{
override fun toString() : String {
val fields = listOf(
this.id.toString(),
this.name,
this.surname,
this.dateOfBirth,
this.phoneNumber,
this.email,
this.address,
this.city,
this.notes)
return fields.reduce{ field , element -> "$field;$element" }
}
}
这是我的活动之一(放置提取数据按钮的位置):
package com.example.contactsform.activityViews
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.activity.result.contract.ActivityResultContracts
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.contactsform.model.ContactModelClass
import com.example.contactsform.adapter.ItemAdapter
import com.example.contactsform.R
import com.example.contactsform.databaseHelper.SQLiteHelper
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class ContactListActivity : AppCompatActivity() {
private lateinit var contactsRecyclerView: RecyclerView
private lateinit var addContactButton : Button
private lateinit var exportDataButton: Button
private val filePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){result->
val data : Intent? = result.data
if (result.resultCode == Activity.RESULT_OK && data!=null){
val selectedFileUri = data.data
if (selectedFileUri != null) {
val selectedFilePath = selectedFileUri.toString()
writeTextToFile(this, selectedFilePath, dataToWriteInFile())
}
}
}
private val FILENAME = "ExportedContacts.txt"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contact_list)
contactsRecyclerView = findViewById(R.id.contactsRecyclerView)
setupListOfDataIntoRecyclerView()
addContactButton= findViewById(R.id.addContactButton)
addContactButton.setOnClickListener {
val changeFormIntent = Intent(this, AddContactActivity::class.java)
startActivity(changeFormIntent)
}
exportDataButton = findViewById(R.id.exportDataButton)
exportDataButton.setOnClickListener {
createContactFile()
}
}
private fun createContactFile() {
val openDirectoryPickerIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
openDirectoryPickerIntent.addCategory(Intent.CATEGORY_OPENABLE)
openDirectoryPickerIntent.type = "text/plain"
openDirectoryPickerIntent.putExtra(Intent.EXTRA_TITLE, FILENAME)
filePicker.launch(openDirectoryPickerIntent)
}
private fun dataToWriteInFile(): String {
val stringBuilder = StringBuilder()
return stringBuilder.appendLine(getContactsList().toString()).toString()
}
private fun writeTextToFile(context: Context, filePath: String ,dataToFile: String) {
val file = File(context.getExternalFilesDir(null), filePath)
try {
val fileOutputStream = FileOutputStream(file)
fileOutputStream.use{stream->
stream.write(dataToFile.toByteArray())
}
}catch (e: IOException){
e.printStackTrace()
}
}
private fun setupListOfDataIntoRecyclerView() {
if (getContactsList().size > 0){
contactsRecyclerView.visibility = View.VISIBLE
contactsRecyclerView.layoutManager = LinearLayoutManager(this)
val itemAdapter = ItemAdapter(this, getContactsList())
contactsRecyclerView.adapter = itemAdapter
} else {
contactsRecyclerView.visibility = View.GONE
}
}
private fun getContactsList(): ArrayList<ContactModelClass> {
val sqliteHelper = SQLiteHelper(this)
return sqliteHelper.loadContacts()
}
}
这是我的数据库助手:
package com.example.contactsform.databaseHelper
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import com.example.contactsform.model.ContactModelClass
import java.util.ArrayList
class SQLiteHelper (context: Context) : SQLiteOpenHelper(context , DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private const val DATABASE_VERSION = 1
private const val DATABASE_NAME = "contacts_form"
private const val TABLE_CONTACTS = "contacts"
private const val ID = "ID"
private const val NAME = "Name"
private const val SURNAME = "Surname"
private const val DATE_OF_BIRTH = "Date_Of_Birth"
private const val PHONE_NUMBER = "Phone_Number"
private const val EMAIL = "Email"
private const val ADDRESS = "Address"
private const val CITY = "City"
private const val NOTES = "Notes"
}
override fun onCreate(db: SQLiteDatabase?) {
val CREATE_CONTACTS_TABLE = ("CREATE TABLE " + TABLE_CONTACTS + "("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ NAME + " TEXT,"
+ SURNAME + " TEXT,"
+ DATE_OF_BIRTH + " TEXT,"
+ PHONE_NUMBER + " TEXT,"
+ EMAIL + " TEXT,"
+ ADDRESS + " TEXT,"
+ CITY + " TEXT,"
+ NOTES + " TEXT)")
db?.execSQL(CREATE_CONTACTS_TABLE)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db!!.execSQL("DROP TABLE IF EXISTS $TABLE_CONTACTS")
onCreate(db)
}
fun insertContact (contact : ContactModelClass) : Long {
val db = this.writableDatabase
var contentValues = ContentValues()
contentValues.put(NAME, contact.name)
contentValues.put(SURNAME, contact.surname)
contentValues.put(DATE_OF_BIRTH, contact.dateOfBirth)
contentValues.put(PHONE_NUMBER, contact.phoneNumber)
contentValues.put(EMAIL, contact.email)
contentValues.put(ADDRESS, contact.address)
contentValues.put(CITY, contact.city)
contentValues.put(NOTES, contact.notes)
val success = db.insertWithOnConflict(TABLE_CONTACTS, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE)
db.close()
return success
}
fun loadContacts() : ArrayList<ContactModelClass> {
val contactList : ArrayList<ContactModelClass> = ArrayList<ContactModelClass>()
val selectQuery = "SELECT * FROM $TABLE_CONTACTS"
val db = this.readableDatabase
var cursor: Cursor? = null
try {
cursor = db.rawQuery(selectQuery, null)
} catch (e: SQLiteException){
db.execSQL(selectQuery)
return ArrayList()
}
var id: Int
var name: String
var surname : String
var dateOfBirth: String
var phoneNumber: String
var email: String
var address: String
var city: String
var notes: String
if (cursor.moveToFirst()){
do {
id = cursor.getInt(cursor.getColumnIndexOrThrow(ID))
name = cursor.getString(cursor.getColumnIndexOrThrow(NAME))
surname = cursor.getString(cursor.getColumnIndexOrThrow(SURNAME))
dateOfBirth = cursor.getString(cursor.getColumnIndexOrThrow(DATE_OF_BIRTH))
phoneNumber = cursor.getString(cursor.getColumnIndexOrThrow(PHONE_NUMBER))
email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL))
address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))
city = cursor.getString(cursor.getColumnIndexOrThrow(CITY))
notes = cursor.getString(cursor.getColumnIndexOrThrow(NOTES))
val contact = ContactModelClass(
id = id,
name = name,
surname = surname,
dateOfBirth = dateOfBirth,
phoneNumber = phoneNumber,
email = email, address = address,
city = city,
notes = notes)
contactList.add(contact)
} while (cursor.moveToNext())
}
return contactList
}
fun deleteContactsTable() {
val db = this.writableDatabase
db?.execSQL("DROP TABLE $TABLE_CONTACTS")
onCreate(db)
}
}
编辑
我更新了活动,但文件仍然创建但为空(代码如下)
class ContactListActivity : AppCompatActivity() {
private val createFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ it ->
val resultData : Intent? = it.data
val selectedFileUri = resultData?.data
if (it.resultCode == Activity.RESULT_OK){
try{
if (resultData != null) {
resultData.data?.let {
if (selectedFileUri != null) {
contentResolver.openOutputStream(selectedFileUri).use { stream ->
stream?.writer()?.write(dataToWriteInFile())
}
}
}
}
} catch (e: IOException){
e.printStackTrace()
}
}
}
private val FILENAME = "ExportedContacts.txt"
------------------------------------------------------------
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contact_list)
exportDataButton = findViewById(R.id.exportDataButton)
exportDataButton.setOnClickListener {
createContactFile()
}
}
------------------------------------------------------------
private fun createContactFile() {
val openDirectoryPickerIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply{
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, FILENAME)
}
createFile.launch(openDirectoryPickerIntent)
}
private fun dataToWriteInFile(): String {
val stringBuilder = StringBuilder()
return stringBuilder.appendLine(getContactsList().toString()).toString()
}
我认为问题在于使用:-
val file = File(context.getExternalFilesDir(null), filePath)
测试时(下面用于测试的代码)这会导致文件未找到捕获异常(因此代码继续)。使用设备资源管理器,目录(请注意,即使显然不需要,但已添加 mkdirs)存在,但在从 writeTextFile 调用返回后不存在文件。
更改为使用:-
val file = File(/*context.getExternalFilesDir(null),*/ filePath)
高兴地继续创建了文件,该文件随后可见,可以通过设备资源管理器打开并查看数据。
注意 进行的测试是精简版本,纯粹是为了检查 writeTextToFile 方法并确定问题(如果有)。用于测试的代码:-
未改变的 SQLiteHelper 和 ContactModel 类
修改以下活动代码以测试 writeTextToFile 函数
:-
class MainActivity : AppCompatActivity() {
private val filePicker =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val data: Intent? = result.data
if (result.resultCode == Activity.RESULT_OK && data != null) {
val selectedFileUri = data.data
if (selectedFileUri != null) {
val selectedFilePath = selectedFileUri.toString()
writeTextToFile(this, selectedFilePath, dataToWriteInFile())
}
}
}
private val FILENAME = "ExportedContacts.txt"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val extFilePath = this.getExternalFilesDir("testing")
var filePath: File? = null
val fileName: String = "testit.txt"
if (extFilePath != null) {
extFilePath.mkdirs()
filePath = File("${extFilePath}${File.separator}${fileName}")
}
val helper = SQLiteHelper(this)
helper.insertContact(ContactModelClass(1,"A","Z","2000-01-01","00 0000 0001","a@email","1 Nowhere Street.","ELSEWHERE","not much to say"))
helper.insertContact(ContactModelClass(2,"B","Z","2000-02-01","00 0000 0002","b@email","1 Nowhere Street.","ELSEWHERE","not much to say"))
helper.insertContact(ContactModelClass(3,"C","Z","2000-03-01","00 0000 0003","c@email","1 Nowhere Street.","ELSEWHERE","not much to say"))
helper.insertContact(ContactModelClass(4,"D","Z","2000-04-01","00 0000 0004","d@email","1 Nowhere Street.","ELSEWHERE","not much to say"))
if (filePath != null) {
writeTextToFile(this,filePath.path,dataToWriteInFile())
}
val breakpoint: String = ""
}
private fun createContactFile() {
val openDirectoryPickerIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
openDirectoryPickerIntent.addCategory(Intent.CATEGORY_OPENABLE)
openDirectoryPickerIntent.type = "text/plain"
openDirectoryPickerIntent.putExtra(Intent.EXTRA_TITLE, FILENAME)
filePicker.launch(openDirectoryPickerIntent)
}
private fun dataToWriteInFile(): String {
val stringBuilder = StringBuilder()
return stringBuilder.appendLine(getContactsList().toString()).toString()
}
private fun writeTextToFile(context: Context, filePath: String, dataToFile: String) {
val file = File(/*context.getExternalFilesDir(null),*/ filePath)
try {
val fileOutputStream = FileOutputStream(file)
val dataAsByteArray = dataToFile.toByteArray()
val breakpoint: String = ""
fileOutputStream.use { stream ->
stream.write(dataToFile.toByteArray())
}
fileOutputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun getContactsList(): ArrayList<ContactModelClass> {
val sqliteHelper = SQLiteHelper(this)
return sqliteHelper.loadContacts()
}
}
最终结果,一个文件包含:-
[1;A;Z;2000-01-01;00 0000 0001;a@email;1 Nowhere Street.;ELSEWHERE;not much to say, 2;B;Z;2000-02-01;00 0000 0002;b@email;1 Nowhere Street.;ELSEWHERE;not much to say, 3;C;Z;2000-03-01;00 0000 0003;c@email;1 Nowhere Street.;ELSEWHERE;not much to say, 4;D;Z;2000-04-01;00 0000 0004;d@email;1 Nowhere Street.;ELSEWHERE;not much to say]
但是,如果将代码更改为使用file的原始设置,则在
val breakpoint: String = ""
函数中的onCreate
行上放置断点后:-
然后运行(卸载已安装的App id后)并单击调试(绿色Bug图标),结果如下:-
在日志中
W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Android/data/a.a.so77247817kotlinsqlitewritetofile/files/storage/emulated/0/Android/data ....
使用设备资源管理器并遵循以下路径:-
您可能仍然需要注意应用于该问题的注释,尽管建议的关闭已包含在 writeTestToFile 函数中。