Tuesday, August 28, 2018

Dependency Injection with KOIN to Androidx Jetpack

Welcome to this post.

A few months ago google launch jetpack. This library includes some new component, like Work Manager. The problem is if you want to use dependency injection, then you can not do easily. If you heard about dependency injection then the first library coming in our mind is Dagger. But the problem is Dagger still not support androidx.

A few days ago I work with a new project, and I decide to use Work manger. But the problem arises because I am used Dagger. I search and waste a couple of hours but the solution is not in the satisfaction level. Then I decide to move to KOIN.

This is the first time I use KOIN. And the problem is gone. And I found KOIN is very handy. I don't have to write a lot of code. Just a simple line configuration. Declaring module is also very easy.
you will see that too in this post.

So let's start KOIN
First, add dependencies of KOIN


implementation 'org.koin:koin-android:0.9.3'
implementation "org.koin:koin-androidx-viewmodel:1.0.0-beta-3"

Note: Don't forget to add room, viewmodel, and livedata dependencies.

Let's Create a database.
I use room.

I skipping this part. you probably know how to create database using room. (This post is about some advanced topics).

Code snippet:
Create a table, such as WordTable
@Entity
class WordTable(
        @PrimaryKey(autoGenerate = true)
        var id: Int = 0,
        var word: String = "",
        var des: String = "",
        var bookmark: Boolean = false,
        var addByUser: Boolean = false,
        var uploaded:Boolean = false
)

Now Data Accessing object(Dao), exp: WordTableDao
@Dao
interface WordTableDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun add(table: WordTable): Long

    @Update
    fun update(table: WordTable): Int

    @Delete
    fun delete(pageTable: WordTable): Int


    @Query("Select * From WordTable")
    fun getAllList(): List<WordTable>
}

All set, create the database class.

@Database(entities = [WordTable::class], version = 1,
        exportSchema = false)
abstract class MyDatabase:RoomDatabase(){
    abstract val wordTableDao: WordTableDao
}

So our class database is ready, but still, we don't create the database. We will create the database in the KOIN module declaration time.

Now create a ViewModel class.
we need only Dao. it will provide by KOIN. We ViewModel Class as the parent class.
In the class just create a method called getData()  and load data from the database and return.

class MainVM(private val wordTableDao: WordTableDao):ViewModel(){

    fun getData() = wordTableDao.getRandomData()

}

Now time for module declaration. See the code first, then I discussing code.
we have two module. The first one is dbModule

val dbModule = module {

    single {
        Room.databaseBuilder(androidContext(), MyDatabase::class.java,
                "SS").build()
    }

    single { get<MyDatabase>().wordTableDao }

}

code -
1. keyword: module, it's just a function that creating a koin module
2. To create an instance you have to use single (from this new version, in the old version, use been keyword)
3. in a single, create the database
4. another single, call get function and type MyDatabase and access the Dao.

The second module is vmModule,
Same as this module but this time use viewmodel instead of single.

val vmModule = module {
    viewModel {
        MainVM(get() as WordTableDao)
    }
}

Note: if you don't use the androidx version of KOIN then you get an error, in this module declaration

we finished all declaration. Now we are ready for using KOIN. But before using KOIN, we have to start KOIN. To start KOIN declare an application class.
and write a simple line of code-

startKoin(this, listOf(module_list)

See code-

class MyApp:Application(){

    override fun onCreate() {
        super.onCreate()
        startKoin(this, listOf(dbModule, vmModule))
    }
}

All set. Now inject the ViewModel in the Main activity.
Now hard here, just declare a variable type MainVM and use by keyword (for lazy injection) and call viewModel() function.

    private val viewModel: MainVM by viewModel()

See code-

class MainActivity : AppCompatActivity() {

    private val viewModel: MainVM by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)


        viewModel.getData().observe(this, Observer {
            //observe data
        })
    }

}

Now the most desired one-
we are going to inject in the worker class. Create a worker class. But this time you are injecting KOIN not supported class. Don't worry. KOIN has already built an option to solve this problem. You have to implement KoinComponent.
After that same, declare a variable and lazy injection call inject() function
    val wordTableDao: WordTableDao by inject()

See the code-

class MyWorker : Worker(), KoinComponent {

    val wordTableDao: WordTableDao by inject()

    override fun doWork(): Result {

        val table = WordTable()

        wordTableDao.add(table)
        
        return Result.SUCCESS
    }


}

That's it. We are done. We finished our goal.

If you check all of those code in Gist also. Click here.

So by using KOIN, you can easily inject on the Androidx component. But for the dagger case, it's not so easy as this.

So, it's your choice, to use dagger or koin.

Thanks for reading.
Hope this articles help you a lot.

Happy coding

Saturday, August 4, 2018

Playing with IO in Kotlin

Welcome to this post.

Today I going to cover a very unusual topic. If this problem is matched with you then it will help you to save a lot of time.

So let's start-

Imagine you have a text file of some data and you want to format it and save the data into the JSON file.

Example text file line:
abbey - n. a monastery ruled by an abbot
....
And you have so many lines in the text file.

and your goal is separate, the word and type(part of speech) and description of the word.
so the file output is like
word - abbey
type - n
des - a monastery ruled by an abbot

and save this on  a JSON file
{
    "word": "Abbey",
    "type": "n",
    "des": "a monastery ruled by an abbot"
}

This is our today goal. (If you don't clear the take a look again)

Let's jump in the code-
Create the main method and add try catch block

fun main(args: Array<String>) {
    try {

    } catch (e: Exception) {
        e.printStackTrace()
    }
}

you have to write all the code in the try-catch block.
Create an input and output file name and path variable
val inputName = "definitions.txt"
val outputName = "Output2.json"
val path = "D:/Android/test/"
make sure you select right path. Now make input and output file.
val inputFile = File("$path$inputName")
val outputFile = File("$path$outputName")

you can check is your input file exists or not
println(inputFile.exists())
Output:
true
Note: If your output is false then your path is wrong, select the right path. until you get this output.

Create an instance of Gson
val gson = Gson()
Note: If you don't know how to add the external library, see this tutorial

Now create a Model class-
class Model(
    val word:String,
    val type:String
    val des:String
)
Create an array list of this model
val list = ArrayList<Model>()

Take a look at our data source. This decorated in lines. So we can go through emery lines.
So we run a for loop for every line. Take the buffer reader from the input file and read lines.

for (string in inputFile.bufferedReader(bufferSize = 4096).readLines()) {
}
I use the buffer size 4096 you can use 1024, or any as you want. If you don't have clear knowledge about buffer size then this answer on StackOverflow

create an instance of StringBuilder.
var stringBuilder = StringBuilder()

Step 1: separate the word.
so we read character wise and if we find this sign '-' then stop reading.
for (char in string) {
     if (char == '-') {
          break
     }
     stringBuilder.append(char)
}
so we find our word and that in the StringBuilder
var word = stringBuilder.toString().trim()
Note: use trim() to remove the space.

Step2: separate the type
first remove the word from the string and do the same code as the word. But this time sign is changed. Now sign is '.'
val typesData = string.removePrefix("$word -").trim()
stringBuilder = StringBuilder()
for (char in typesData){
     if (char == '.') {
         break
     }
     stringBuilder.append(char)
}
so we find the type
val type = stringBuilder.toString().trim()

Step 3: separate the description
first, remove the types
val des = typesData.removePrefix("$type.").trim()
so we find the description.

Extra:
we want to capitalize the first char of the word
val cap = word[0].toUpperCase()
word = word.replaceFirst(word[0],cap)

Now create the model and add to the list.
val model = Model(word,type,des)
list.add(model)

So our all word and tyes and des added in the array.

Now our final task is to convert this list to JSON. For this task we use Gson.
//convert list to JSON string
val string = gson.toJson(list)

Create a new output file on the disk.
//crate new file
outputFile.createNewFile()

Now create the instance of file writer and write this json code in the file.
//now write
val writer = FileWriter(outputFile)
writer.write(string)
Don't forget to close the writer.
//close writer
writer.close()

and finally, print a done message.
println("Done")

So everything is done. Now run the program

Output:
Done

So our JSON file:

[{"word":"Abbey","type":"n","des":"a monastery ruled by an abbot"},{"word":"Abide","type":"v","des":"dwell; inhabit or live in"},{"word":"Abound","type":"v","des":"be abundant or plentiful; exist in large quantities"},{"word":"Absence","type":"n","des":"the state of being absent"},{"word":"Absorb","type":"v","des":"assimilate or take in"},{"word":"Abstinence","type":"n","des":"practice of refraining from indulging an appetite especially alcohol"},{"word":"Absurd","type":"j","des":"inconsistent with reason or logic or common sense"},{"word":"Abundant","type":"j","des":"present in great quantity"},{"word":"Abusive","type":"j","des":"characterized by physical or psychological maltreatment"}, ... 

Check the full code on GitHub.

Some other problem:

Problem 1:
This time our output design will same but our input design will be different.
The input pattern is
word    (type) description

See the solution on Github 

Problem 2:
Input pattern:
word = (type) description

Check the solution on Github

That's it today. Today I cover some unusual topics, but this will help you to save time if you have the same problem.

Thank you very much for reading.

Happy coding.