Showing posts with label android. Show all posts
Showing posts with label android. Show all posts
Monday, June 15, 2020

Belajar Aplikasi Android Studi Kasus Proyek Akademi : Menghubungkan Activity & Fragment


Codelab Menghubungkan Activity dan Fragment

Pada codelab ini Anda akan menghubungkan tiap asset-asset dan layout dengan fragment dan Activity. Mari kita mulai dengan mengubah kode yang ada di dalam package ui:

  1. Buatlah sebuah kelas AcademyAdapter untuk menampilkan item untuk RecyclerView di package academy.
    4-j4CfW8zLqnFHb6v_ZlubbGLyps2-rKhilq_phtHafSmyd2iGN6zqAqlbtPiiY4BJB_y4VSY7_mD337jatwQXfitwB-uCZdJpG5Somx_64nR9uSFO4orjWT4ZGvl_V29rs65rUs
    Tambahkanlah kode berikut untuk setCourses, membuat kelas ViewHolder, mem-binding ViewHolder dan mengirim data ke DetailActivity.
    Kotlin
    class AcademyAdapter : RecyclerView.Adapter<AcademyAdapter.CourseViewHolder>() {
    private var listCourses = ArrayList<CourseEntity>()

    fun setCourses(courses: List<CourseEntity>?) {
    if (courses == null) return
    listCourses.clear()
    listCourses.addAll(courses)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CourseViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_academy, parent, false)
    return CourseViewHolder(view)
    }

    override fun onBindViewHolder(holder: CourseViewHolder, position: Int) {
    val course = listCourses[position]
    holder.bind(course)
    }

    override fun getItemCount(): Int = listCourses.size


    class CourseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(course: CourseEntity) {
    with(itemView) {
    tv_item_title.text = course.title
    tv_item_description.text = course.description
    tv_item_date.text = resources.getString(R.string.deadline_date, course.deadline)
    setOnClickListener {
    val intent = Intent(context, DetailCourseActivity::class.java).apply {
    putExtra(DetailCourseActivity.EXTRA_COURSE, course.courseId)
    }
    context.startActivity(intent)
    }
    Glide.with(context)
    .load(course.imagePath)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(img_poster)
    }
    }
    }
    }
    Java
    public class AcademyAdapter extends RecyclerView.Adapter<AcademyAdapter.CourseViewHolder> {
    private List<CourseEntity> listCourses = new ArrayList<>();

    void setCourses(List<CourseEntity> listCourses) {
    if (listCourses == null) return;
    listCourses.clear();
    listCourses.addAll(listCourses);
    }

    @NonNull
    @Override
    public CourseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_academy, parent, false);
    return new CourseViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final CourseViewHolder holder, int position) {
    CourseEntity course = listCourses.get(position);
    holder.bind(course);
    }

    @Override
    public int getItemCount() {
    return listCourses.size();
    }

    class CourseViewHolder extends RecyclerView.ViewHolder {
    final TextView tvTitle;
    final TextView tvDescription;
    final TextView tvDate;
    final ImageView imgPoster;

    CourseViewHolder(View itemView) {
    super(itemView);
    tvTitle = itemView.findViewById(R.id.tv_item_title);
    imgPoster = itemView.findViewById(R.id.img_poster);
    tvDescription = itemView.findViewById(R.id.tv_item_description);
    tvDate = itemView.findViewById(R.id.tv_item_date);
    }

    void bind(CourseEntity course) {
    tvTitle.setText(course.getTitle());
    tvDescription.setText(course.getDescription());
    tvDate.setText(itemView.getResources().getString(R.string.deadline_date, course.getDeadline()));
    itemView.setOnClickListener(v -> {
    Intent intent = new Intent(itemView.getContext(), DetailCourseActivity.class);
    intent.putExtra(DetailCourseActivity.EXTRA_COURSE, course.getCourseId());
    itemView.getContext().startActivity(intent);
    });
    Glide.with(itemView.getContext())
    .load(course.getImagePath())
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading).error(R.drawable.ic_error))
    .into(imgPoster);
    }
    }
    }
    Pada kode di atas akan terjadi error pada bagian EXTRA_COURSE, klik ALT + Enter kemudian pilih:
    Kotlin
    2019121714133292b0d98e4bf996e5be9ac105d3b3f727.png
    Java
    201912171413552caa27ccb7656db4f0c7ece733118129.jpeg
    Setelah itu masukkan “extra_course” sebagai value-nya.
    Kotlin
    companion object {
    const val EXTRA_COURSE = "extra_course"
    }
    Java
    public static final String EXTRA_COURSE = "extra_course";
  2. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam AcademyFragment untuk menghubungkan fragment dengan RecyclerView.
    Kotlin
    class AcademyFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
    inflater.inflate(R.layout.fragment_academy, container, false)


    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val courses = DataDummy.generateDummyCourses()
    val academyAdapter = AcademyAdapter()

    academyAdapter.setCourses(courses)

    with(rv_academy) {
    layoutManager = LinearLayoutManager(context)
    setHasFixedSize(true)
    adapter = academyAdapter
    }
    }
    }

    }
    Java
    public class AcademyFragment extends Fragment {
    private RecyclerView rvCourse;
    private ProgressBar progressBar;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_academy, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    rvCourse = view.findViewById(R.id.rv_academy);
    progressBar = view.findViewById(R.id.progress_bar);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    List<CourseEntity> courses = DataDummy.generateDummyCourses();

    AcademyAdapter academyAdapter = new AcademyAdapter();
    academyAdapter.setCourses(courses);

    rvCourse.setLayoutManager(new LinearLayoutManager(getContext()));
    rvCourse.setHasFixedSize(true);
    rvCourse.setAdapter(academyAdapter);
    }
    }

    }
    Dengan begitu, kelas AcademyFragment sudah menampilkan data dari kelas DataDummy.
  3. Selanjutnya buatlah kembali kelas baru dan beri nama BookmarkAdapter di package bookmark.
    jxMR5ZbRYEhipS_XlgmggXrglKSB70ffL6oAq8HidgNNhCPnPLSssts9dlFSdTaDi3dx_yI1k_g7w0J3NNt8kxGilNoPOtOehgs-aqBH8y8Grj8l912QIvdTNvfAKOGJolTSLpsP
    Tambahkanlah kode berikut untuk setCourses, membuat kelas ViewHolder, mem-binding ViewHolder dan mengirim data ke DetailActivity.
    Kotlin
    class BookmarkAdapter(private val callback: BookmarkFragmentCallback) : RecyclerView.Adapter<BookmarkAdapter.CourseViewHolder>() {
    private val listCourses = ArrayList<CourseEntity>()

    fun setCourses(courses: List<CourseEntity>?) {
    if (courses == null) return
    listCourses.clear()
    listCourses.addAll(courses)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CourseViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_bookmark, parent, false)
    return CourseViewHolder(view)
    }

    override fun onBindViewHolder(holder: CourseViewHolder, position: Int) {
    val course = listCourses[position]
    holder.bind(course)
    }

    override fun getItemCount(): Int = listCourses.size

    inner class CourseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(course: CourseEntity){
    with(itemView){
    tv_item_title.text = course.title
    tv_item_description.text = course.description
    tv_item_date.text = resources.getString(R.string.deadline_date, course.deadline)
    setOnClickListener {
    val intent = Intent(context, DetailCourseActivity::class.java).apply {
    putExtra(DetailCourseActivity.EXTRA_COURSE, course.courseId)
    }
    context.startActivity(intent)
    }
    img_share.setOnClickListener { callback.onShareClick(course) }
    Glide.with(context)
    .load(course.imagePath)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(img_poster)
    }
    }
    }
    }
    Java
    public class BookmarkAdapter extends RecyclerView.Adapter<BookmarkAdapter.CourseViewHolder> {
    private final BookmarkFragmentCallback callback;
    private ArrayList<CourseEntity> listCourses = new ArrayList<>();

    BookmarkAdapter(BookmarkFragmentCallback callback) {
    this.callback = callback;
    }

    void setCourses(List<CourseEntity> courses) {
    if (courses == null) return;
    listCourses.clear();
    listCourses.addAll(courses);
    }

    @NonNull
    @Override
    public CourseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_bookmark, parent, false);
    return new CourseViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final CourseViewHolder holder, int position) {
    CourseEntity course = listCourses.get(position);
    holder.bind(course);
    }

    @Override
    public int getItemCount() {
    return listCourses.size();
    }

    class CourseViewHolder extends RecyclerView.ViewHolder {
    final TextView tvTitle;
    final TextView tvDescription;
    final TextView tvDate;
    final ImageButton imgShare;
    final ImageView imgPoster;

    CourseViewHolder(View itemView) {
    super(itemView);
    tvTitle = itemView.findViewById(R.id.tv_item_title);
    tvDescription = itemView.findViewById(R.id.tv_item_description);
    tvDate = itemView.findViewById(R.id.tv_item_date);
    imgShare = itemView.findViewById(R.id.img_share);
    imgPoster = itemView.findViewById(R.id.img_poster);
    }

    void bind(CourseEntity course) {
    tvTitle.setText(course.getTitle());
    tvDescription.setText(course.getDescription());
    tvDate.setText(itemView.getResources().getString(R.string.deadline_date, course.getDeadline()));
    itemView.setOnClickListener(v -> {
    Intent intent = new Intent(itemView.getContext(), DetailCourseActivity.class);
    intent.putExtra(DetailCourseActivity.EXTRA_COURSE, course.getCourseId());
    itemView.getContext().startActivity(intent);
    });
    imgShare.setOnClickListener(v -> callback.onShareClick(course));
    Glide.with(itemView.getContext())
    .load(course.getImagePath())
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading).error(R.drawable.ic_error))
    .into(imgPoster);
    }
    }
    }
    Pada kode di atas akan terjadi eror pada BookmarkFragmentCallback. Klik ALT + Enter kemudian pilih Create interface BookmarkFragmentCallback’.
    Kotlin
    20191217160115411c37d386620f225479953e9ca412a0.png
    Java
    20191217160008b7d22c18346e322e03cee8454f420ed9.jpeg
    Klik OK, maka secara otomatis akan ada kelas baru di dalam package bookmark.
    AAAy-WFSyiEnziHeQIhasJRVhGrM51GrBcag8iFO8sTCDuD7S80L_BvhAwFG2k-1HH_8ZJdXjEBo0TJhFV4dWO-nocw6eW4itXPI47RU7I8x5bMF0KO4753dTnptjKYLGPZN4fKv
    Setelah itu tambahkan kode berikut di kelas BookmarkFragmentCallback:
    Kotlin
    interface BookmarkFragmentCallback {
    fun onShareClick(course: CourseEntity)
    }
    Java
    interface BookmarkFragmentCallback {
    void onShareClick(CourseEntity course);
    }
  4. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam BookmarkFragment untuk menghubungkan Fragment dengan RecyclerView.
    Kotlin
    class BookmarkFragment : Fragment(), BookmarkFragmentCallback {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_bookmark, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val courses = DataDummy.generateDummyCourses()
    val adapter = BookmarkAdapter(this)

    adapter.setCourses(courses)

    with(rv_bookmark) {
    layoutManager = LinearLayoutManager(context)
    setHasFixedSize(true)
    this.adapter = adapter
    }
    }
    }

    override fun onShareClick(course: CourseEntity) {
    if (activity != null) {
    val mimeType = "text/plain"
    ShareCompat.IntentBuilder.from(activity).apply {
    setType(mimeType)
    setChooserTitle("Bagikan aplikasi ini sekarang.")
    setText(resources.getString(R.string.share_text, course.title))
    startChooser()
    }
    }
    }

    }
    Java
    public class BookmarkFragment extends Fragment implements BookmarkFragmentCallback {
    private RecyclerView rvBookmark;
    private ProgressBar progressBar;

    public BookmarkFragment() {
    // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_bookmark, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    rvBookmark = view.findViewById(R.id.rv_bookmark);
    progressBar = view.findViewById(R.id.progress_bar);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    List<CourseEntity> courses = DataDummy.generateDummyCourses();

    BookmarkAdapter adapter = new BookmarkAdapter(this);
    adapter.setCourses(courses);

    rvBookmark.setLayoutManager(new LinearLayoutManager(getContext()));
    rvBookmark.setHasFixedSize(true);
    rvBookmark.setAdapter(adapter);
    }
    }

    @Override
    public void onShareClick(CourseEntity course) {
    if (getActivity() != null) {
    String mimeType = "text/plain";
    ShareCompat.IntentBuilder
    .from(getActivity())
    .setType(mimeType)
    .setChooserTitle("Bagikan aplikasi ini sekarang.")
    .setText(getResources().getString(R.string.share_text, course.getTitle()))
    .startChooser();
    }
    }

    }
    Dengan begitu, kelas BookmarkFragment sudah bisa menampilkan data dari kelas DataDummy.
  5. Selanjutnya, buatlah kembali kelas baru dan beri nama DetailCourseAdapter di package detail.
    yv8AmDT2z9Z4KcEZHC05RvMDWa5R69BmGnL-3tl6Vc-atEylqVHL8VRQK6XUxf6DjJrXM-DddyEarWzzwWmDz-9UzdOtnndIzqlwYGaAzrdf7xLrl8-vuMvdIEV95N78IOjfqtji
    Tambahkanlah kode berikut untuk setModules, membuat kelas ViewHolder, mem-binding ViewHolder dan mengirim data ke CourseReaderActivity.
    Kotlin
    class DetailCourseAdapter : RecyclerView.Adapter<DetailCourseAdapter.ModuleViewHolder>() {

    private val listModules = ArrayList<ModuleEntity>()

    fun setModules(modules: List<ModuleEntity>?) {
    if (modules == null) return
    listModules.clear()
    listModules.addAll(modules)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModuleViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_module_list, parent, false)
    return ModuleViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ModuleViewHolder, position: Int) {
    val module = listModules[position]
    viewHolder.bind(module)
    }

    override fun getItemCount(): Int = listModules.size

    inner class ModuleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(module: ModuleEntity) {
    with(itemView) {
    text_module_title.text = module.title
    }
    }
    }
    }
    Java
    public class DetailCourseAdapter extends RecyclerView.Adapter<DetailCourseAdapter.ModuleViewHolder> {

    private List<ModuleEntity> listModules = new ArrayList<>();

    void setModules(List<ModuleEntity> modules) {
    if (modules == null) return;
    listModules.clear();
    listModules.addAll(modules);
    }

    @NonNull
    @Override
    public ModuleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_module_list, parent, false);
    return new ModuleViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ModuleViewHolder viewHolder, int position) {
    ModuleEntity module = listModules.get(position);
    viewHolder.bind(module);
    }

    @Override
    public int getItemCount() {
    return listModules.size();
    }

    class ModuleViewHolder extends RecyclerView.ViewHolder {
    final TextView textTitle;

    ModuleViewHolder(View itemView) {
    super(itemView);
    textTitle = itemView.findViewById(R.id.text_module_title);
    }

    void bind(ModuleEntity module) {
    textTitle.setText(module.getTitle());
    }
    }
    }
  6. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam DetailCourseActivity untuk menghubungkan Activity dengan RecyclerView.
    Kotlin
    class DetailCourseActivity : AppCompatActivity() {

    companion object {
    const val EXTRA_COURSE = "extra_course"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_detail_course)
    setSupportActionBar(toolbar)
    supportActionBar?.setDisplayHomeAsUpEnabled(true)

    val adapter = DetailCourseAdapter()

    val extras = intent.extras
    if (extras != null) {
    val courseId = extras.getString(EXTRA_COURSE)
    if (courseId != null) {
    val modules = DataDummy.generateDummyModules(courseId)
    adapter.setModules(modules)
    for(course in DataDummy.generateDummyCourses()) {
    if(course.courseId == courseId) {
    populateCourse(course)
    }
    }
    }
    }

    with(rv_module) {
    isNestedScrollingEnabled = false
    layoutManager = LinearLayoutManager(this@DetailCourseActivity)
    setHasFixedSize(true)
    this.adapter = adapter
    val dividerItemDecoration = DividerItemDecoration(rv_module.context, DividerItemDecoration.VERTICAL)
    addItemDecoration(dividerItemDecoration)
    }
    }

    private fun populateCourse(courseEntity: CourseEntity) {
    text_title.text = courseEntity.title
    text_desc.text = courseEntity.description
    text_date.text = resources.getString(R.string.deadline_date, courseEntity.deadline)

    Glide.with(this)
    .load(courseEntity.imagePath)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(image_poster)

    btn_start.setOnClickListener {
    val intent = Intent(this@DetailCourseActivity, CourseReaderActivity::class.java).apply {
    putExtra(CourseReaderActivity.EXTRA_COURSE_ID, courseEntity.courseId)
    }
    startActivity(intent)
    }
    }
    }
    Java
    public class DetailCourseActivity extends AppCompatActivity {

    public static final String EXTRA_COURSE = "extra_course";
    private Button btnStart;
    private TextView textTitle;
    private TextView textDesc;
    private TextView textDate;
    private ImageView imagePoster;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_detail_course);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    if (getSupportActionBar() != null) {
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    btnStart = findViewById(R.id.btn_start);
    textTitle = findViewById(R.id.text_title);
    textDesc = findViewById(R.id.text_description);
    textDate = findViewById(R.id.text_date);
    RecyclerView rvModule = findViewById(R.id.rv_module);
    imagePoster = findViewById(R.id.image_poster);

    DetailCourseAdapter adapter = new DetailCourseAdapter();

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
    String courseId = extras.getString(EXTRA_COURSE);
    if (courseId != null) {
    List<ModuleEntity> modules = DataDummy.generateDummyModules(courseId);
    adapter.setModules(modules);

    for (int i = 0; i < DataDummy.generateDummyCourses().size(); i++) {
    CourseEntity courseEntity = DataDummy.generateDummyCourses().get(i);
    if (courseEntity.getCourseId().equals(courseId)) {
    populateCourse(courseEntity);
    }
    }
    }
    }

    rvModule.setNestedScrollingEnabled(false);
    rvModule.setLayoutManager(new LinearLayoutManager(this));
    rvModule.setHasFixedSize(true);
    rvModule.setAdapter(adapter);
    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(rvModule.getContext(), DividerItemDecoration.VERTICAL);
    rvModule.addItemDecoration(dividerItemDecoration);
    }

    private void populateCourse(CourseEntity courseEntity) {
    textTitle.setText(courseEntity.getTitle());
    textDesc.setText(courseEntity.getDescription());
    textDate.setText(getResources().getString(R.string.deadline_date, courseEntity.getDeadline()));

    Glide.with(this)
    .load(courseEntity.getImagePath())
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(imagePoster);

    btnStart.setOnClickListener(v -> {
    Intent intent = new Intent(DetailCourseActivity.this, CourseReaderActivity.class);
    intent.putExtra(CourseReaderActivity.EXTRA_COURSE_ID, courseEntity.getCourseId());
    startActivity(intent);
    });
    }
    }
    Pada kode di atas akan terjadi eror pada bagian EXTRA_COURSE_ID, klik ALT + Enter kemudian pilih Create constant field ‘EXTRA_COURSE_ID’ in ‘CourseReaderActivity’.
    Kotlin
    20191217161214b8d64d181a09e3430427096ca3b72eaa.png
    Java

    201912171610012c895dd78f09d9764bdea209660d2846.jpeg
    Setelah itu masukkan “extra_course_id” sebagai value-nya.
    Kotlin
    companion object {
    const val EXTRA_COURSE_ID = "extra_course_id"
    }
    Java
    public static final String EXTRA_COURSE_ID = "extra_course_id";
  7. Selanjutnya, buatlah kembali kelas baru dan beri nama SectionsPagerAdapter di package home. Kemudian tambahkan kode berikut:
    Kotlin
    class SectionsPagerAdapter(private val mContext: Context, fm: FragmentManager) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    companion object {
    @StringRes
    private val TAB_TITLES = intArrayOf(R.string.home, R.string.bookmark)
    }

    override fun getItem(position: Int): Fragment =
    when (position) {
    0 -> AcademyFragment()
    1 -> BookmarkFragment()
    else -> Fragment()
    }

    override fun getPageTitle(position: Int): CharSequence? = mContext.resources.getString(TAB_TITLES[position])

    override fun getCount(): Int = 2

    }
    Java
    public class SectionsPagerAdapter extends FragmentPagerAdapter {

    @StringRes
    private static final int[] TAB_TITLES = new int[]{R.string.home, R.string.bookmark};
    private final Context mContext;

    SectionsPagerAdapter(Context context, FragmentManager fm) {
    super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    mContext = context;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
    switch (position){
    case 0:
    return new AcademyFragment();
    case 1:
    return new BookmarkFragment();
    default:
    return new Fragment();
    }
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
    return mContext.getResources().getString(TAB_TITLES[position]);
    }

    @Override
    public int getCount() {
    return 2;
    }
    }
  8. Bukalah HomeActivity, hubungkan AcademyFragment dan BookmarkFragment dengan HomeActivity. Tambahkan kode berikut:
    Kotlin
    class HomeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_home)

    val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager)
    view_pager.adapter = sectionsPagerAdapter
    tabs.setupWithViewPager(view_pager)

    supportActionBar?.elevation = 0f


    }
    }
    Java
    public class HomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
    ViewPager viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(sectionsPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);

    if (getSupportActionBar() != null) {
    getSupportActionBar().setElevation(0);
    }

    }
    }
  9. Anda sudah membuat halaman Academy, Bookmark dan Detail Course. Sampai sini Anda bisa mencobanya dahulu. Langkah selanjunya, Anda akan membuat halaman untuk list Module dan Detail Module. Sebelum itu, tambahkan sebuah kelas baru di package reader.
    uZt5IMIA_f4YQnQnXaK_AbWVc47RcyYKU04jZtTzMhgOddQwFFGI3VBp2Y1B-hYh7LbmGz_5vqQTeCXyK8gOshJsaCvQn6JmF_FSRNljfDmYP0ZPZStvTY2TsFHxhBnyNCw-DmvF
    Setelah itu tambah kode berikut:
    Kotlin
    interface CourseReaderCallback {
    fun moveTo(position: Int, moduleId: String)
    }
    Java
    public interface CourseReaderCallback {
    void moveTo(int position, String moduleId);
    }
    CourseReaderCallback nantinya akan digunakan untuk pindah dari halaman satu ke halaman lain.
  10. Bukalah ModuleContentFragment dan ubah kode di dalamnya untuk menampilkan data dummy di WebView.
    Kotlin
    class ModuleContentFragment : Fragment() {

    companion object {
    val TAG = ModuleContentFragment::class.java.simpleName

    fun newInstance(): ModuleContentFragment {
    return ModuleContentFragment()
    }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_content, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val content = ContentEntity("<h3 class=\\\"fr-text-bordered\\\">Contoh Content</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>")
    populateWebView(content)
    }
    }

    private fun populateWebView(content: ContentEntity) {
    web_view.loadData(content.content, "text/html", "UTF-8")
    }

    }
    Java
    public class ModuleContentFragment extends Fragment {
    public static final String TAG = ModuleContentFragment.class.getSimpleName();

    private WebView webView;

    public ModuleContentFragment() {
    // Required empty public constructor
    }

    public static ModuleContentFragment newInstance() {
    return new ModuleContentFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_content, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    webView = view.findViewById(R.id.web_view);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    ContentEntity content = new ContentEntity("<h3 class=\\\"fr-text-bordered\\\">Contoh Content</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>");
    populateWebView(content);
    }
    }

    private void populateWebView(ContentEntity content) {
    webView.loadData(content.getContent(), "text/html", "UTF-8");
    }

    }
    Dengan begitu, kelas ModuleContentFragment sudah bisa menampilkan data dummy di WebView.
  11. Sebelum masuk ke ModuleListFragment, buatlah kembali kelas adapter baru dan beri nama ModuleListAdapter di package reader.list.
    ojxrzYOWgSoAQGmMdFI1RHoz-jv8BEImNSDo8c2edItXGrc8K9HD0V_5kdkIhnF73Us9GBqnmoK4_7LKePEzPJh4Q8d7gvrEvqXMGDmSXLrjobr-A72YrdAQaM-H_0ViVmvrD0RU
    Selanjutnya, ubahlah kode pada kelas tersebut:
    Kotlin
    class ModuleListAdapter internal constructor(private val listener: MyAdapterClickListener) : RecyclerView.Adapter<ModuleListAdapter.ModuleViewHolder>() {
    private val listModules = ArrayList<ModuleEntity>()

    internal fun setModules(modules: List<ModuleEntity>?) {
    if (modules == null) return
    listModules.clear()
    listModules.addAll(modules)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModuleViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_module_list_custom, parent, false)
    return ModuleViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ModuleViewHolder, position: Int) {
    val module = listModules[position]
    viewHolder.bind(module)
    viewHolder.itemView.setOnClickListener {
    listener.onItemClicked(viewHolder.adapterPosition, listModules[viewHolder.adapterPosition].moduleId)
    }
    }

    override fun getItemCount(): Int = listModules.size

    inner class ModuleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val textTitle: TextView = itemView.findViewById(R.id.text_module_title)
    fun bind(module: ModuleEntity) {
    textTitle.text = module.title
    }
    }
    }

    internal interface MyAdapterClickListener {
    fun onItemClicked(position: Int, moduleId: String)
    }
    Java
    public class ModuleListAdapter extends RecyclerView.Adapter<ModuleListAdapter.ModuleViewHolder> {

    private final MyAdapterClickListener listener;
    private List<ModuleEntity> listModules = new ArrayList<>();

    ModuleListAdapter(MyAdapterClickListener listener) {
    this.listener = listener;
    }

    void setModules(List<ModuleEntity> listModules) {
    if (listModules == null) return;
    listModules.clear();
    listModules.addAll(listModules);
    }

    @NonNull
    @Override
    public ModuleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new ModuleViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.items_module_list_custom, parent, false));
    }

    @Override
    public void onBindViewHolder(ModuleViewHolder viewHolder, int position) {
    ModuleEntity module = listModules.get(position);
    viewHolder.bind(module);
    viewHolder.itemView.setOnClickListener(v ->
    listener.onItemClicked(viewHolder.getAdapterPosition(), listModules.get(viewHolder.getAdapterPosition()).getModuleId())
    );
    }

    @Override
    public int getItemCount() {
    return listModules.size();
    }

    class ModuleViewHolder extends RecyclerView.ViewHolder {
    final TextView textTitle;

    ModuleViewHolder(View itemView) {
    super(itemView);
    textTitle = itemView.findViewById(R.id.text_module_title);
    }

    void bind(ModuleEntity module) {
    textTitle.setText(module.getTitle());
    }
    }
    }

    interface MyAdapterClickListener {
    void onItemClicked(int position, String moduleId);
    }
    Pada kelas CourseReaderAdapter terdapat sebuah interface MyAdapterClickListener yang nantinya digunakan untuk berpindah ke halaman ModuleContentFragment.
  12. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam ModuleListFragment untuk menghubungkan Fragment dengan RecyclerView.
    Kotlin
    class ModuleListFragment : Fragment(), MyAdapterClickListener {

    companion object {
    val TAG = ModuleListFragment::class.java.simpleName

    fun newInstance(): ModuleListFragment = ModuleListFragment()
    }

    private lateinit var adapter: ModuleListAdapter
    private lateinit var courseReaderCallback: CourseReaderCallback

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_list, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    adapter = ModuleListAdapter(this)
    populateRecyclerView(DataDummy.generateDummyModules("a14"))
    }

    override fun onAttach(context: Context) {
    super.onAttach(context)
    courseReaderCallback = context as CourseReaderActivity
    }

    override fun onItemClicked(position: Int, moduleId: String) {
    courseReaderCallback.moveTo(position, moduleId)
    }

    private fun populateRecyclerView(modules: List<ModuleEntity>) {
    progress_bar.visibility = View.GONE
    adapter.setModules(modules)
    with(rv_module) {
    layoutManager = LinearLayoutManager(context)
    setHasFixedSize(true)
    adapter = adapter
    }
    val dividerItemDecoration = DividerItemDecoration(rv_module.context, DividerItemDecoration.VERTICAL)
    rv_module.addItemDecoration(dividerItemDecoration)
    }
    }
    Java
    public class ModuleListFragment extends Fragment implements MyAdapterClickListener {

    public static final String TAG = ModuleListFragment.class.getSimpleName();
    private ModuleListAdapter adapter;
    private CourseReaderCallback courseReaderCallback;
    private RecyclerView recyclerView;
    private ProgressBar progressBar;

    public ModuleListFragment() {
    // Required empty public constructor
    }

    public static ModuleListFragment newInstance() {
    return new ModuleListFragment();
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_list, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    recyclerView = view.findViewById(R.id.rv_module);
    progressBar = view.findViewById(R.id.progress_bar);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    adapter = new ModuleListAdapter(this);
    populateRecyclerView(DataDummy.generateDummyModules("a14"));
    }

    }

    @Override
    public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    courseReaderCallback = ((CourseReaderActivity) context);
    }

    @Override
    public void onItemClicked(int position, String moduleId) {
    courseReaderCallback.moveTo(position, moduleId);
    }

    private void populateRecyclerView(List<ModuleEntity> modules) {
    progressBar.setVisibility(View.GONE);
    adapter.setModules(modules);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    recyclerView.setHasFixedSize(true);
    recyclerView.setAdapter(adapter);
    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
    recyclerView.addItemDecoration(dividerItemDecoration);
    }
    }
  13. Ketika Anda memanggil generateDummyModules(), variable “a14” adalah sampel inputan data academy. Nanti akan kita ubah sesuai dengan posisi Fragment yang akan diklik.
    Dan dengan menambahkan kode di atas, maka akan terjadi eror dibagian kode ini:
    Kotlin
    courseReaderCallback = context as CourseReaderActivity
    Java
    courseReaderCallback = ((CourseReaderActivity) context);
    Ini terjadi karena CourseReaderActivity belum mengimplementasikan CourseReaderCallback.
  14. Bukalah CourseReaderActivity, hubungkan ModuleContentFragment dan ModuleListFragment dengan CourseReaderActivity.
    Kotlin
    class CourseReaderActivity : AppCompatActivity(), CourseReaderCallback {

    companion object {
    const val EXTRA_COURSE_ID = "extra_course_id"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_course_reader)

    val bundle = intent.extras
    if (bundle != null) {
    val courseId = bundle.getString(EXTRA_COURSE_ID)
    if (courseId != null) {
    populateFragment()
    }
    }
    }

    override fun moveTo(position: Int, moduleId: String) {
    val fragment = ModuleContentFragment.newInstance()
    supportFragmentManager.beginTransaction().add(R.id.frame_container, fragment, ModuleContentFragment.TAG)
    .addToBackStack(null)
    .commit()
    }

    override fun onBackPressed() {
    if (supportFragmentManager.backStackEntryCount <= 1) {
    finish()
    } else {
    super.onBackPressed()
    }
    }

    private fun populateFragment() {
    val fragmentTransaction = supportFragmentManager.beginTransaction()
    var fragment = supportFragmentManager.findFragmentByTag(ModuleListFragment.TAG)
    if (fragment == null) {
    fragment = ModuleListFragment.newInstance()
    fragmentTransaction.add(R.id.frame_container, fragment, ModuleListFragment.TAG)
    fragmentTransaction.addToBackStack(null)
    }
    fragmentTransaction.commit()
    }
    }
    Java
    public class CourseReaderActivity extends AppCompatActivity implements CourseReaderCallback {

    public static final String EXTRA_COURSE_ID = "extra_course_id";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_course_reader);
    CourseReaderViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);

    Bundle bundle = getIntent().getExtras();
    if (bundle != null) {
    String courseId = bundle.getString(EXTRA_COURSE_ID);
    if (courseId != null) {
    viewModel.setCourseId(courseId);
    populateFragment();
    }
    }
    }

    @Override
    public void moveTo(int position, String moduleId) {
    Fragment fragment = ModuleContentFragment.newInstance();
    getSupportFragmentManager().beginTransaction().add(R.id.frame_container, fragment, ModuleContentFragment.TAG)
    .addToBackStack(null)
    .commit();
    }

    @Override
    public void onBackPressed() {
    if (getSupportFragmentManager().getBackStackEntryCount() <= 1) {
    finish();
    } else {
    super.onBackPressed();
    }
    }

    private void populateFragment() {
    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    Fragment fragment = getSupportFragmentManager().findFragmentByTag(ModuleListFragment.TAG);
    if (fragment == null) {
    fragment = ModuleListFragment.newInstance();
    fragmentTransaction.add(R.id.frame_container, fragment, ModuleListFragment.TAG);
    fragmentTransaction.addToBackStack(null);
    }
    fragmentTransaction.commit();
    }
    }
    Metode moveTo digunakan untuk memanggil ModuleContentFragment sesuai dengan posisi dan moduleId yang dipilih.

  15. Inilah saat yang ditunggu-tunggu. Kita akan menjalankan aplikasi. Silakan jalankan aplikasi yang Anda buat, hasilnya akan seperti ini:201912171702204eec34888068aa412dcc54b05f22996e.gif

Bedah Kode

Proyek Academy ini terdiri dari 3 Activity yakni HomeActivity, DetailCourseActivity dan CourseReaderActivity. Dalam proyek ini juga mempunyai 4 Fragment yakni AcademyFragment, BookmarkFragment, ModuleListFragment dan ModuleContentFragment. Secara fungsi atau kegunaan akan jadi seperti ini:
  • HomeActivity: Menampilkan 2 Fragment (AcademyFragment dan BookmarkFragment) dan sebagai halaman utama dari Aplikasi.
  • DetailCourseActivity: Menampilkan detail Course dan menampilkan list Module yang ada tiap Course-nya. Selain itu di halaman ini juga nantinya akan ada tombol bookmark untuk menyimpan course yang Anda suka.
  • CourseReader: Menampilkan 2 Fragment(ModuleListFragment dan ModuleContentFragment) dan di Activity ini nanti akan ada 2 tampilan yakni untuk ukuran layar yang besar dan kecil.
  • AcademyFragment: Digunakan untuk menampilkan semua Course.
  • BookmarkFragment: Digunakan untuk menampilkan semua Course yang sudah Anda bookmark.
  • ModuleListFragment: Digunakan untuk menampilkan semua Module sesuai Course yang dipilih.
  • ModuleContentFragment: Menampilkan Content dari Module yang dipilih.

Selain itu, proyek Academy nantinya akan menerapkan berbagai komponen Android Jetpack seperti ViewModel, LiveData, Room dan Pagination. Selain itu ada materi pendukung juga seperti Repository, IdleResource, UnitTesting, InstrumentalTest, dll. Tentunya materi akan dibahas secara bertahap. Yang paling keren, proyek Academy akan menggunakan prinsip Offline-Online sehingga ketika data tidak ada di local, maka akan request data dari network dan semua data akan disimpan ke local pada saat itu juga.
Jika Anda kesulitan untuk melakukan langkah-langkah pada latihan kali ini, Anda bisa unduh proyek starter-nya di sini.

Belajar Tahap Unit Testing Androidx Jetpack


Unit Testing

Unit Testing merupakan sebuah pengujian yang memvalidasi unit kode secara individual. Tujuan dari unit testing adalah untuk memastikan bahwa setiap unit perangkat lunak dapat berjalan sesuai fungsi yang sudah ditentukan.


Telah disebutkan sebelumnya bahwa di dalam unit test, kita tidak memerlukan perangkat Android atau emulator untuk menjalankan pengujian, melainkan IDE (Android Studio) saja. Kemudian hasil dari pengujian akan ditampilkan pada konsol Android Studio.

Unit test berfungsi untuk menguji logika bisnis dalam sebuah aplikasi. Dengan demikian kita harus menuliskannya hanya untuk menguji fungsi kecil/unit kode demi memastikan apakah logika kerjanya sudah sesuai dengan yang diharapkan. Misalnya, menguji apakah hasil dari (3 x 3) sama dengan 9.

Kita akan menggunakan library JUnit untuk melakukan unit test. Library ini secara otomatis sudah ditambahkan ketika kita membuat proyek baru pada Android Studio. JUnit ini hanya digunakan untuk menjalankan tes, dengan begitu ia tidak akan di-compile ketika kita menjalankan aplikasi pada perangkat Android atau emulator. Tentunya ini bisa mengurangi ukuran dari APK.

testImplementation 'junit:junit:4.12'

Okay, mari kita coba untuk melakukan pengujian sederhana menggunakan JUnit pada sebuah logika kode. Anggap saja Anda telah memiliki sebuah kelas Utils dengan kode berikut:

Kotlin

class Utils {
@SuppressLint("SimpleDateFormat")
fun toSimpleString(date: Date): String {
return SimpleDateFormat("EEE, dd MM yyy").format(date)
}

}
Java
public class Utils {
@SuppressLint("SimpleDateFormat")
public String toSimpleString(Date date){
return new SimpleDateFormat("EEE, dd MM yyy").format(date);
}

}

Fungsi di atas bisa kita gunakan untuk mengubah date menjadi string dengan format yang sudah ditentukan. 

Sekarang kita akan menambahkan sebuah unit test pada fungsi tersebut untuk menguji apakah hasilnya sesuai atau tidak dengan yang kita harapkan. Untuk menambahkan unit test, tekan SHIFT + CTRL + T pada fungsi tersebut dan pilih Create new test ..., maka akan muncul dialog seperti berikut:
2019070915021298e9c37aad3b1f62d8d520eeca3859a1
Berikan tanda centang dan klik OK maka akan muncul sebuah dialog baru untuk memilih tujuan penyimpanan dari kelas pengujian yang akan dibuat.
20190709150306133e2bb43eed16528b894551efb16341
Karena ini adalah sebuah unit test, maka Anda harus memilih folder ../test/.. untuk menyimpannya. Silakan pilih folder tersebut dan klik OK. Android Studio secara otomatis akan membuatkan Anda sebuah kelas testing dengan nama UtilsTest di mana di dalamnya sudah terdapat sebuah test function dari fungsi sebelumnya. Anda bisa mengubah nama dari fungsi tersebut sesuai yang Anda inginkan.
Kotlin
class UtilsTest {
@Test
fun toSimpleString() {
}
}
Java
public class UtilsTest {
@Test
public void toSimpleString() {
}
}
Anotasi @Test di atas akan memberitahu JUnit bahwa fungsi yang dilampirkan dapat dijalankan sebagai sebuah test case. Sekarang Anda bisa menambahkan kode untuk melakukan pengujian di dalam fungsi toSimpleString() di atas.
Kotlin
@Test
fun toSimpleString() {
val dateFormat: DateFormat = SimpleDateFormat("MM/dd/yyyy")
var date: Date? = null
try {
date = dateFormat.parse("02/28/2018")
} catch (e: ParseException) {
e.printStackTrace()
}
assertEquals("Wed, 28 Feb 2018", Utils.toSimpleString(date))

}
Java
@Test
public void toSimpleString() {
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
Date date = null;
try {
date = dateFormat.parse("02/28/2018");
} catch (ParseException e) {
e.printStackTrace();
}
assertEquals("Wed, 28 Feb 2018", Utils.toSimpleString(date));

}
Perhatikan contoh kode di atas. Kita telah menambahkan fungsi assertEquals() dari JUnit untuk memastikan bahwa kedua parameter di dalamnya memiliki nilai yang sama. Parameter pertama dari fungsi tersebut adalah nilai yang diharapkan, sedangkan parameter kedua adalah nilai yang dihasilkan dari fungsi toSimpleString(). Sekarang coba Anda jalankan pengujian tersebut dengan menekan SHIFT + CTRL + F10 atau klik kanan pada file UtilsTest dan pilih Run ‘UtilsTest’. Selanjutnya akan muncul hasil pengujian pada konsol seperti berikut:
20190709161654786e19f9be55d832eefca3302f09e82e
Dari hasil pengujian di atas, kita bisa melihat bahwa pengujian yang telah kita lakukan gagal, ditunjukan dengan indikator warna merah pada konsol. Kegagalan tersebut disebabkan nilai yang dikeluarkan oleh fungsi toSimpleString() berbeda dengan nilai yang kita harapkan.
Coba perhatikan lagi hasil pengujian yang tertera pada konsol di atas. Di sana terdapat log error yang menunjukan bahwa nilai yang kita harapkan adalah Wed, 28 Feb 2018 sedangkan nilai yang dihasilkan oleh fungsi toSimpleString() adalah Wed, 28 02 2018. Dengan demikian, kita bisa memastikan bahwa di dalam fungsi toSimpleString() terdapat kode yang kurang tepat.
Sekarang coba kita buka kembali kelas Utils dan temukan letak kesalahan pada fungsi toSimpleString(). Di sana kita menentukan sebuah pola (pattern) dari SimpleDateFormat dengan "EEE, dd MM yyy", di mana MM tersebut akan menghasilkan output dari Month dengan format angka, misal: 02. Format tersebut berbeda dengan format yang kita harapkan, dan menyebabkan pengujian gagal. Silakan ubah pola dari SimpleDateFormat menjadi "EEE, dd MMM yyy":
Kotlin
@SuppressLint("SimpleDateFormat")
fun toSimpleString(date: Date?): String? {
return SimpleDateFormat("EEE, dd MMM yyy").format(date)
}
Java
@SuppressLint("SimpleDateFormat")
public static String toSimpleString(Date date){
return new SimpleDateFormat("EEE, dd MMM yyy").format(date);
}
Jalankanlah kembali pengujiannya. Seharusnya pengujian akan berhasil, sebagaimana ditandai oleh indikator warna hijau seperti berikut:
201907091623136a2ae26535b44e8e89a54c94a8729179
Saturday, June 13, 2020

Belajar Coroutines DI Kotlin Terlengkap


Memulai Coroutines

Untuk lebih memahami tentang coroutines, mari kita mulai mencobanya langkah demi langkah. Hal pertama yang wajib Anda tahu adalah bahwa coroutines bukanlah bagian dari bahasa Kotlin. Coroutines hanyalah library lain yang disediakan oleh JetBrains. Untuk itu, agar bisa menggunakannya Anda perlu menambahkan dependensi berikut pada build.gradle.kts:


  1. dependencies {

  2.     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2")

  3. }


Dengan menambahkan dependensi di atas, kini Anda sudah siap untuk mencoba menggunakan fitur-fitur coroutines dalam membuat program concurrency. Yuk kita mulai dari kode yang sangat sederhana berikut ini:

  1. import kotlinx.coroutines.*

  2.  

  3. fun main() = runBlocking{

  4.     launch {

  5.         delay(1000L)

  6.         println("Coroutines!")

  7.     }

  8.     println("Hello,")

  9.     delay(2000L)

  10. }


Kode di atas menggambarkan bagaimana coroutines bekerja. Kita menggunakan fungsi runBlocking untuk memulai coroutine utama dan launch untuk menjalankan coroutine baru. Jika Anda menjalankan program tersebut, maka konsol akan menampilkan hasil ini:
Hello,
Coroutines!
Kata Hello, akan ditampilkan lebih awal dan kata Coroutines! Akan ditampilkan 1 detik setelahnya. Mengapa demikian? Padahal jika diperhatikan, kode untuk menampilkan kata Coroutines! dituliskan lebih dulu.
Fungsi delay(1000L) di dalam launch digunakan untuk menunda kode berikutnya selama 1 detik. delay adalah fungsi yang spesial pada coroutines. Ia merupakan sebuah suspending function yang tidak akan memblokir sebuah thread.
Selama proses penundaan tersebut, main thread akan terus berjalan sehingga fungsi println("Hello,") akan langsung dijalankan. Setelah 1 detik, baru fungsi println("Coroutines!") akan dijalankan.
delay(2000L) digunakan untuk menunda selama 2 detik sebelum JVM berakhir. Tanpa kode ini, JVM akan langsung berhenti ketika kode terakhir dijalankan, sehingga kode di dalam launch tidak akan pernah dijalankan.
Ini baru sekedar permulaan loh. Masih banyak lagi fungsi-fungsi menarik lain pada coroutines yang dapat mempermudah kita dalam membuat program concurrency. 
Anda bisa memanfaatkan kumpulan library yang dapat ditemukan pada repositori kotlinx.coroutines. JetBrains selaku tim pengembang juga berkomitmen untuk mempertahankan backward compatibility untuk tiap perubahan yang akan dirilis. Itulah mengapa coroutines sudah diperkenalkan pada Kotlin versi 1.1.
Tersedia juga panduan resmi untuk langkah-langkah penerapan coroutines. Ikuti saja tautan ini

Coroutines Builder

Pada modul sebelumnya kita sudah mencoba menggunakan fungsi runBlocking dan launch untuk memulai sebuah coroutines. Kedua fungsi tersebut merupakan coroutines builder, yaitu sebuah fungsi yang mengambil suspending lambda dan membuat coroutine untuk menjalankannya.
Kotlin menyediakan beberapa coroutine builder yang bisa disesuaikan dengan berbagai macam skenario, seperti:
  • launch
    Seperti yang sudah kita coba sebelumnya, fungsi ini digunakan untuk memulai sebuah coroutines yang tidak akan mengembalikan sebuah hasil. launch akan menghasilkan Job yang bisa kita gunakan untuk membatalkan eksekusi.
  • runBlocking
    Fungsi ini dibuat untuk menjembatani blocking code menjadi kode yang dapat ditangguhkan. runBlocking akan memblokir sebuah thread yang sedang berjalan hingga eksekusi coroutine selesai. Seperti contoh sebelumnya, kita bisa menggunakannya pada fungsi main() dan bisa juga untuk penerapan unit test
  • async
    Kebalikan dari launch, fungsi ini digunakan untuk memulai sebuah coroutine yang akan mengembalikan sebuah hasil. Ketika menggunakannya, Anda harus berhati-hati karena ia akan menangkap setiap exception yang terjadi di dalam coroutine. Jadi async akan mengembalikan Deferred yang berisi hasil atau exception. Ketika yang dikembalikan adalah exception, maka Anda harus siap untuk menanganinya.
Sekarang giliran kita untuk mencoba contoh penerapan coroutine dengan async. Bayangkan jika kita memiliki 2 (dua) suspending function seperti berikut:

  1. suspend fun getCapital(): Int {

  2.     delay(1000L)

  3.     return 50000

  4. }

  5.  

  6. suspend fun getIncome(): Int {

  7.     delay(1000L)

  8.     return 75000

  9. }


Anggap saja bahwa delay pada kedua fungsi tersebut adalah waktu yang dibutuhkan untuk melakukan operasi sebelum hasilnya didapatkan. Selanjutnya kita ingin memanfaatkan keduanya, misalnya untuk menghitung keuntungan seperti berikut:

  1. import kotlinx.coroutines.*

  2.  

  3. fun main() = runBlocking {

  4.     val capital = getCapital()

  5.     val income = getIncome()

  6.     println("Your profit is ${income - capital}")

  7. }


Pada kode di atas, kita menggunakan pendekatan sequential. Kenapa? Pada dasarnya kode di dalam coroutines juga dijalankan secara berurutan seperti kode normal lain. Dalam praktiknya kita melakukan ini jika kita menggunakan hasil dari fungsi pertama untuk membuat keputusan apakah kita perlu memanggil fungsi kedua.
Bagaimana jika tidak ada ketergantungan antara fungsi getCapital dan getIncome dan kita ingin menjalankan keduanya secara bersamaan? Di sinilah async dibutuhkan. Kita bisa menuliskan kode seperti berikut:

  1. import kotlinx.coroutines.*

  2.  

  3. fun main() = runBlocking {

  4.     val capital = async { getCapital() }

  5.     val income = async { getIncome() }

  6.     println("Your profit is ${income.await() - capital.await()}")

  7. }


Dengan kode tersebut, kita telah memanggil fungsi getCapital dan getIncome di dalam async.Maka async akan mengembalikan hasil dari masing-masing fungsi. Lalu untuk mengakses hasil tersebut, kita perlu menggunakan fungsi await.
Wait.. adakah perbedaan dengan kode sebelumnya? Dengan async seolah-olah kedua fungsi tersebut berjalan bersamaan dan membutuhkan waktu yang lebih singkat dari kode sebelumnya. Untuk membuktikannya, yuk coba jalankan kode berikut:
  1. import kotlinx.coroutines.*
  2. import kotlin.system.measureTimeMillis
  3.  
  4. fun main() = runBlocking {
  5.     val timeOne = measureTimeMillis {
  6.         val capital = getCapital()
  7.         val income = getIncome()
  8.         println("Your profit is ${income - capital}")
  9.     }
  10.  
  11.     val timeTwo = measureTimeMillis {
  12.         val capital = async { getCapital() }
  13.         val income = async { getIncome() }
  14.         println("Your profit is ${income.await() - capital.await()}")
  15.     }
  16.  
  17.     println("Completed in $timeOne ms vs $timeTwo ms")
  18.  
  19. }

Konsol akan menampilkan hasil berikut:
Your profit is 25000
Your profit is 25000
Completed in 2013 ms vs 1025 ms
Kita bisa lihat bahwa kode yang dijalankan di dalam async bisa selesai hampir 2 kali lebih cepat dibandingkan tanpa async!

Job and Deferred

Secara umum, fungsi asynchronous pada coroutines terbagi menjadi 2 (dua) jenis, yaitu fungsi yang mengembalikan hasil dan sebaliknya, fungsi yang tidak mengembalikan hasil. Fungsi yang mengembalikan hasil biasanya digunakan jika kita menginginkan sebuah data ketika fungsi tersebut selesai dijalankan. Sebagai contoh, fungsi untuk mengambil informasi dari web service yang menghasilkan respon berupa JSON atau yang lainnya. Sedangkan fungsi yang tidak mengembalikan hasil biasanya digunakan untuk mengirimkan analitik, menuliskan log, atau tugas sejenis lainnya.
Sebagai developer, tentunya kita menginginkan tetap bisa mengakses fungsi yang sudah dijalankan. Misalnya, ketika kita ingin membatalkan tugasnya atau memberikan instruksi tambahan ketika fungsi tersebut telah mencapai kondisi tertentu. Untuk bisa melakukannya, Anda perlu memahami tentang Job dan Deferred pada coroutines.

Job

Job adalah sebuah hasil dari perintah asynchronous yang dijalankan. Objek dari job akan merepresentasikan coroutine yang sebenarnya. Sebuah job akan memiliki 3 (tiga) properti yang nantinya bisa dipetakan ke dalam setiap state atau keadaan. Berikut adalah ketiga properti tersebut:
  1. isActive
    Sebuah properti yang menunjukkan ketika sebuah job sedang aktif.
  2. isCompleted
    Sebuah properti yang menunjukkan ketika sebuah job telah selesai.
  3. isCancelled
    Sebuah properti yang menunjukkan ketika sebuah job telah dibatalkan.
Pada dasarnya, job akan segera dijalankan setelah ia dibuat. Namun kita juga bisa membuat sebuah job tanpa menjalankannya. Job memiliki beberapa siklus hidup mulai dari pertama kali ia dibuat hingga akhirnya selesai. Kira-kira seperti inilah siklus dari sebuah job jika digambarkan dalam sebuah diagram:
2019042910202001a0e80e2a6d2f6cdbd36cb544a69416.png

Dari diagram di atas, kita bisa melihat bahwa job akan melewati beberapa state. Pada setiap state tersebut nantinya kita bisa memberikan instruksi sesuai yang kita inginkan. Sebelum kita mengolahnya, mari pahami terlebih dahulu semua state yang ada pada sebuah job.
  • New
    Keadaan di mana sebuah job telah diinisialisasi namun belum pernah dijalankan.
  • Active
    Sebuah job akan memiliki status aktif ketika ia sedang berjalan. Dalam hal ini, job yang sedang ditangguhkan (suspended job) juga termasuk ke dalam job yang aktif.
  • Completed
    Ketika job sudah tidak berjalan lagi. Ini berlaku untuk job yang berakhir secara normal, dibatalkan, ataupun karena suatu pengecualian.
  • Cancelling
    Suatu kondisi ketika fungsi cancel() dipanggil pada job yang sedang aktif dan memerlukan waktu untuk pembatalan tersebut selesai.
  • Cancelled
    Keadaan yang dimiliki oleh sebuah job yang sudah berhasil dibatalkan. Perlu diketahui bahwa job yang dibatalkan juga dapat dianggap sebagai Completed job

Membuat Job Baru

Job dapat diinisialisasikan menggunakan fungsi launch() maupun Job() seperti berikut:

  1. //menggunakan launch():

  2. fun main() = runBlocking {

  3.     val job = launch {

  4.         // Do background task here

  5.     }

  6. }

  7.  

  8. //menggunakan Job():

  9. fun main() = runBlocking {

  10.     val job = Job()

  11. }


Setelah diinisialisasikan, job akan memiliki state New dan akan langsung dijalankan. Jika Anda ingin membuat sebuah job tanpa langsung menjalankannya, Anda bisa memanfaatkan CoroutineStart.LAZY seperti berikut:

  1. fun main() = runBlocking {

  2.     val job = launch(start = CoroutineStart.LAZY) {

  3.         TODO("Not implemented yet!")

  4.     }

  5. }


Dengan begitu job tersebut bisa dijalankan saat nanti dibutuhkan.

Menjalankan Job

Setelah membuat sebuah job, kini kita bisa mulai menjalankan job tersebut. Caranya pun cukup sederhana, kita bisa menggunakan fungsi start() seperti berikut:

  1. fun main() = runBlocking {

  2.     val job = launch(start = CoroutineStart.LAZY) {

  3.         delay(1000L)

  4.         println("Start new job!")

  5.     }

  6.  

  7.     job.start()

  8.     println("Other task")

  9. }


Atau bisa juga dengan menggunakan fungsi join():

  1. fun main() = runBlocking {

  2.     val job = launch(start = CoroutineStart.LAZY) {

  3.         delay(1000L)

  4.         println("Start new job!")

  5.     }

  6.  

  7.     job.join()

  8.     println("Other task")

  9. }


Perbedaan dari keduanya adalah bahwa yang start() akan memulai job tanpa harus menunggu job tersebut selesai, sedangkan join() akan menunda eksekusi sampai job selesai. Jika kode pertama dijalankan, maka konsol akan menampilkan hasil berikut:
Other task
Start new job!
Sedangkan kode kedua akan menampilkan hasil:
Start new job!
Other task
Setelah dijalankan,  job akan memiliki state Active

Membatalkan Job

Ibarat pekerjaan di dunia nyata, sebuah job seharusnya bisa dibatalkan. Hanya job yang sedang aktif yang dapat dibatalkan. Anda bisa melakukannya dengan memanggil fungsi cancel() seperti berikut:

  1. fun main() = runBlocking {

  2.     val job = launch {

  3.         delay(5000)

  4.         println("Start new job!")

  5.     }

  6.  

  7.     delay(2000)

  8.     job.cancel()

  9.     println("Cancelling job...")

  10.     if (job.isCancelled){

  11.         println("Job is cancelled")

  12.     }

  13. }


Kode di atas menggambarkan sebuah job membutuhkan waktu 5 detik untuk dijalankan. Namun ketika mencapai waktu 2 detik, job tersebut telah dibatalkan. Saat fungsi cancel() dipanggil, job akan memasuki state Cancelling sampai pembatalan tersebut berhasil. Kemudian setelah pembatalan berhasil, job akan memiliki state Cancelled dan Completed.
Perlu diketahui bahwa jika cancel() dipanggil dalam job baru yang belum dijalankan, job tersebut tidak akan melalui state Cancelling, melainkan akan langsung memasuki state Cancelled.
Kita juga bisa menambahkan parameter terhadap fungsi cancel(), yaitu parameter cause yang bisa digunakan untuk memberitahu kenapa sebuah job dibatalkan.

  1. job.cancel(cause = CancellationException("Time is up!"))


CancellationException akan mengirimkan nilainya sebagai pengecualian dari job tersebut. Kita pun bisa mengakses nilai tersebut dengan fungsi getCancellationException. Karena getCancellationException masih tahap eksperimen, Anda perlu menambahkan anotasi @InternalCoroutinesApi. Cobalah modifikasi dan jalankan kode Anda:

  1. @InternalCoroutinesApi

  2. fun main() = runBlocking {

  3.     val job = launch {

  4.         delay(5000)

  5.         println("Start new job!")

  6.     }

  7.  

  8.     delay(2000)

  9.     job.cancel(cause = CancellationException("time is up!"))

  10.     println("Cancelling job...")

  11.     if (job.isCancelled){

  12.         println("Job is cancelled because ${job.getCancellationException().message}")

  13.     }

  14. }


Konsol akan menampilkan hasil berikut:
Cancelling job...
Job is cancelled because time is up!

Deferred

Seperti yang sudah disampaikan sebelumnya di bagian coroutines builder,  fungsi async akan mengembalikan nilai deferred yang berupa hasil atau exception. Deferred adalah nilai tangguhan yang dihasilkan dari proses coroutines. Nilai ini nantinya bisa kita kelola sesuai dengan kebutuhan. 
Deferred dapat kita ciptakan secara manual. Meskipun begitu, dalam praktiknya, jarang kita membuat deferred secara manual. Biasanya kita hanya bekerja dengan deferred yang dihasilkan oleh async.
Deferred juga memiliki life cycle yang sama dengan job. Perbedaanya hanyalah pada tipe hasil yang diberikan. Selain memberikan hasil ketika proses komputasi sukses, ia juga bisa memberikan hasil saat proses tersebut gagal. Hasil dari deferred tersedia ketika mencapai state completed dan dapat diakses dengan fungsi await. Deferred akan mengirimkan pengecualian jika ia telah gagal. Kita bisa mengakses nilai pengecualian tersebut dengan fungsi getCompletionExceptionOrNull.
Pada dasarnya, nilai deferred juga merupakan sebuah job. Ia diciptakan dan dimulai pada saat coroutines mencapai state active. Bagaimanapun, fungsi async juga memiliki opsional parameter seperti CoroutineStart.LAZY untuk memulainya. Dengan begitu, deferred juga bisa diaktifkan saat fungsi startjoin, atau await dipanggil.
Di modul sebelumnya kita sudah membahas kode berikut ini:

  1. import kotlinx.coroutines.*

  2.  

  3. fun main() = runBlocking {

  4.     val capital = async { getCapital() }

  5.     val income = async { getIncome() }

  6.     println("Your profit is ${income.await() - capital.await()}")

  7. }


capital dan income adalah contoh dari nilai deferred yang untuk mengaksesnya kita membutuhkan fungsi await

Coroutine Dispatcher

Seperti yang sudah kita ketahui, coroutines berjalan di atas sebuah thread. Tentunya kita harus mengetahui thread mana yang akan digunakan untuk menjalankan dan melanjutkan sebuah coroutine. Untuk menentukannya kita membutuhkan sebuah base class bernama CoroutineDispatcher. Di dalam kelas tersebut kita akan menemukan beberapa objek yang nantinya bisa digunakan untuk menentukan thread yang berfungsi menjalankan coroutines.
  • Dispatcher.Default
    Merupakan dispatcher dasar yang digunakan oleh semua standard builders seperti launchasync, dll jika tidak ada dispatcher lain yang ditentukan. Dispatcher.Default menggunakan kumpulan thread yang ada pada JVM. Pada dasarnya, jumlah maksimal thread yang digunakan adalah sama dengan jumlah core dari CPU.
    Untuk menggunakannya, Anda cukup menggunakan coroutines builder tanpa harus menuliskan dispatcher secara spesifik:

    1. launch {

    2.     // TODO: Implement suspending lambda here

    3. }


    Namun Anda juga tetap diperbolehkan untuk menuliskannya secara eksplisit:

    1. launch(Dispatcher.Default){

    2.     // TODO: Implement suspending lambda here

    3. }


  • Dispatcher.IOSebuah dispatcher yang dapat digunakan untuk membongkar pemblokiran operasi I/O. Ia akan menggunakan kumpulan thread yang dibuat berdasarkan permintaan. Anda bisa menerapkannya dengan menambahkan Dispatcher.IO pada coroutines builder:

    1. launch(Dispatcher.IO){

    2.     // TODO: Implement algorithm here

    3. }


  • Dispatcher.Unconfined
    Dispatcher ini akan menjalankan coroutines pada thread yang sedang berjalan sampai mencapai titik penangguhan. Setelah penangguhan, coroutines akan dilanjutkan pada thread dimana komputasi penangguhan yang dipanggil.
    Sebagai contoh, ketika fungsi a memanggil fungsi b, yang dijalankan dengan dispatcher dalam thread tertentu, fungsi a akan dilanjutkan dalam thread yang sama dengan fungsi b dijalankan. Perhatikan kode berikut:

    1. import kotlinx.coroutines.*

    2.  

    3. fun main() = runBlocking<Unit> {

    4.     launch(Dispatchers.Unconfined) {

    5.         println("Starting in ${Thread.currentThread().name}")

    6.         delay(1000)

    7.         println("Resuming in ${Thread.currentThread().name}")

    8.     }.start()

    9. }


    Jika dijalankan maka konsol akan menampilkan hasil berikut:
    Starting in mainResuming in kotlinx.coroutines.DefaultExecutorArtinya, coroutine telah dimulai dari main thread, kemudian tertunda oleh fungsi delay selama 1 detik. Setelah itu, coroutine dilanjutkan kembali pada thread DefaultExecutor.

Bersamaan dengan objek-objek tersebut, ada beberapa builder yang dapat digunakan untuk menentukan thread yang dibutuhkan:
  • Single Thread ContextDispatcher ini menjamin bahwa setiap saat coroutine akan dijalankan pada thread yang Anda tentukan. Untuk menerapkannya, Anda bisa memanfaatkan newSingleThreadContext() seperti kode dibawah ini:

    1. import kotlinx.coroutines.*

    2.  

    3. fun main() = runBlocking<Unit> {

    4.     val dispatcher = newSingleThreadContext("myThread")

    5.     launch(dispatcher) {

    6.         println("Starting in ${Thread.currentThread().name}")

    7.         delay(1000)

    8.         println("Resuming in ${Thread.currentThread().name}")

    9.     }.start()

    10. }


    Jalankan kode tersebut, seharusnya konsol akan menampilkan hasil berikut:
    Starting in myThreadResuming in myThread
    Walaupun sudah menjalankan fungsi delay, coroutine akan tetap berjalan pada myThread.
  • Thread PoolSebuah dispatcher yang memiliki kumpulan thread. Ia akan memulai dan melanjutkan coroutine di salah satu thread yang tersedia pada kumpulan tersebut. Runtime akan menentukan thread mana yang tersedia dan juga menentukan bagaimana proses distribusi bebannya.
    Anda bisa menerapkan thread pool dengan fungsi newFixedThreadPoolContext() seperti berikut:

    1. import kotlinx.coroutines.*

    2.  

    3. fun main() = runBlocking<Unit> {

    4.     val dispatcher = newFixedThreadPoolContext(3, "myPool")

    5.  

    6.     launch(dispatcher) {

    7.         println("Starting in ${Thread.currentThread().name}")

    8.         delay(1000)

    9.         println("Resuming in ${Thread.currentThread().name}")

    10.     }.start()

    11. }


    Pada kode di atas, kita telah menetapkan thread myPool sebanyak 3 thread. Runtime akan secara otomatis menentukan pada thread mana coroutine akan dijalankan dan dilanjutkan. Hasil dari kode tersebut adalah:
    Starting in myPool-1
    Resuming in myPool-2

Channels

Kita sudah belajar bagaimana membuat dan mengelola coroutines. Seperti kita ketahui, sebuah program dapat memiliki banyak thread dan dalam beberapa thread bisa terdapat jutaan coroutines. Lalu, bagaimana jika ada 2 (dua) coroutines yang saling ingin berinteraksi satu sama lain? Channels adalah jawabnya.
Beberapa masalah yang muncul pada concurrency seperti deadlock, race conditions, dan lainnya, sering kali dipicu oleh satu hal, apa itu? Rupanya problem pembagian memori atau sumber daya antar thread. Untuk mengatasinya, banyak programming language seperti GoDart, dan juga Kotlin telah menyediakan channels.
Channels adalah nilai deferred yang menyediakan cara mudah untuk mentransfer nilai tunggal antara coroutine. Pada dasarnya, channels sangat mirip dengan BlockingQueue. Namun, alih-alih memblokir sebuah thread, channels menangguhkan sebuah coroutine yang jauh lebih ringan. Untuk lebih memahaminya, mari simak kode di bawah ini:

  1. import kotlinx.coroutines.*

  2. import kotlinx.coroutines.channels.Channel

  3.  

  4. fun main() = runBlocking(CoroutineName("main")) {

  5.     val channel = Channel<Int>()

  6.     launch(CoroutineName("v1coroutine")){

  7.         println("Sending from ${Thread.currentThread().name}")

  8.         for (x in 1..5) channel.send(x * x)

  9.     }

  10.  

  11.     repeat(5) { println(channel.receive()) }

  12.     println("received in ${Thread.currentThread().name}")

  13. }


Kode di atas akan menghasilkan hasil berikut:
Sending from main @v1coroutine#2
1
4
9
16
25
received in main @main#1
Bisa dilihat bahwa pada coroutine v1coroutine bahwa channels telah mengirimkan nilai dari hasil komputasi dengan menggunakan fungsi send. Setelah itu, di coroutine lain (main) channel menerima nilai dengan menggunakan fungsi receive.
Kesimpulannya, channels memungkinkan komunikasi yang aman antar kode concurrent. Ia membuat kode concurrent dapat berkomunikasi dengan mengirim dan menerima pesan tanpa harus peduli di thread mana coroutine berjalan. 
Selengkapnya tentang channel Anda bisa mempelajarinya pada tautan ini