diff --git a/GuessTheWordDataBinding/README.md b/GuessTheWordDataBinding/README.md new file mode 100644 index 0000000..a9a2c0e --- /dev/null +++ b/GuessTheWordDataBinding/README.md @@ -0,0 +1,45 @@ +ViewModel and LiveData with Data Binding - Solution Code +================================== + +This is the starter code for the ViewModel and LiveData with Data Binding codelab. + +Introduction +------------ + +This starter app is a two player game, GuessTheWord. It is a word guessing app you can play with one or more friends. To play, hold the device in landscape, facing away from you with your thumbs on the **Skip** and **Got It** buttons. Your friends can then give you clues to help you guess the word. If you get the word right, press **Got It**. If you're stuck, press **Skip** or you can use the **End** button to end the game. +This code demostrates how to use Data Binding with the Android Architecture components, LiveData and ViewModel. + +Pre-requisites +-------------- + +You need to know: +- How to open, build, and run Android apps with Android Studio. +- How to use the Navigation Architecture Component +- Passing the data between navigation destinations. +- Read the logs using the Logcat. + + +Getting Started +--------------- + +1. Download and run the app. + +License +------- + +Copyright 2019 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy of +the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. diff --git a/GuessTheWordDataBinding/app/.gitignore b/GuessTheWordDataBinding/app/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/GuessTheWordDataBinding/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/GuessTheWordDataBinding/app/build.gradle b/GuessTheWordDataBinding/app/build.gradle new file mode 100755 index 0000000..b282b87 --- /dev/null +++ b/GuessTheWordDataBinding/app/build.gradle @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' +apply plugin: "androidx.navigation.safeargs" + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.example.android.guesstheword" + minSdkVersion 19 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + dataBinding { + enabled = true + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.12' + + // KTX + implementation 'androidx.core:core-ktx:1.0.1' + + // Navigation + implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0" + implementation "android.arch.navigation:navigation-ui-ktx:1.0.0" + + //ViewModel and LiveData + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' +} diff --git a/GuessTheWordDataBinding/app/proguard-rules.pro b/GuessTheWordDataBinding/app/proguard-rules.pro new file mode 100755 index 0000000..f1b4245 --- /dev/null +++ b/GuessTheWordDataBinding/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/GuessTheWordDataBinding/app/src/androidTest/java/com/example/android/guesstheword/ExampleInstrumentedTest.kt b/GuessTheWordDataBinding/app/src/androidTest/java/com/example/android/guesstheword/ExampleInstrumentedTest.kt new file mode 100755 index 0000000..20f64d6 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/androidTest/java/com/example/android/guesstheword/ExampleInstrumentedTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword + +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.example.android.guesstheword", appContext.packageName) + } +} diff --git a/GuessTheWordDataBinding/app/src/main/AndroidManifest.xml b/GuessTheWordDataBinding/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..5e86e0e --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/GuessTheWordDataBinding/app/src/main/ic_guess_it-web.png b/GuessTheWordDataBinding/app/src/main/ic_guess_it-web.png new file mode 100755 index 0000000..e78aef1 Binary files /dev/null and b/GuessTheWordDataBinding/app/src/main/ic_guess_it-web.png differ diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/MainActivity.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/MainActivity.kt new file mode 100755 index 0000000..8d99816 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/MainActivity.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +/** + * Creates an Activity that hosts all of the fragments in the app + */ +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.main_activity) + } + +} diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt new file mode 100755 index 0000000..a7cd6f1 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword.screens.game + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.navigation.fragment.NavHostFragment.findNavController +import com.example.android.guesstheword.R +import com.example.android.guesstheword.databinding.GameFragmentBinding + +/** + * Fragment where the game is played + */ +class GameFragment : Fragment() { + + private lateinit var binding: GameFragmentBinding + + private lateinit var viewModel: GameViewModel + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + // Inflate view and obtain an instance of the binding class + binding = DataBindingUtil.inflate( + inflater, + R.layout.game_fragment, + container, + false + ) + Log.i("GameFragment", "Called ViewModelProviders.of") + + viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java) + + // Set the viewmodel for databinding - this allows the bound layout access + // to all the data in the VieWModel + binding.gameViewModel = viewModel + + // Specify the current activity as the lifecycle owner of the binding. + // This is used so that the binding can observe LiveData updates + binding.lifecycleOwner = this + + // Observer for the Game finished event + viewModel.eventGameFinish.observe(this, Observer { hasFinished -> + if (hasFinished) gameFinished() + }) + + return binding.root + } + + private fun gameFinished() { + Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show() + val action = GameFragmentDirections.actionGameToScore() + action.score = viewModel.score.value?:0 + findNavController(this).navigate(action) + viewModel.onGameFinishComplete() + } +} diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt new file mode 100644 index 0000000..d2f60c7 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword.screens.game + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** + * ViewModel containing all the logic needed to run the game + */ +class GameViewModel : ViewModel() { + + // The current _word + private val _word = MutableLiveData() + val word: LiveData + get() = _word + + // The current score + private val _score = MutableLiveData() + val score: LiveData + get() = _score + + // Countdown time + private val _eventGameFinish = MutableLiveData() + val eventGameFinish: LiveData + get() = _eventGameFinish + + + // The list of words - the front of the list is the next _word to guess + private lateinit var wordList: MutableList + + + /** + * Resets the list of words and randomizes the order + */ + private fun resetList() { + wordList = mutableListOf( + "queen", + "hospital", + "basketball", + "cat", + "change", + "snail", + "soup", + "calendar", + "sad", + "desk", + "guitar", + "home", + "railway", + "zebra", + "jelly", + "car", + "crow", + "trade", + "bag", + "roll", + "bubble" + ) + wordList.shuffle() + } + + init { + _word.value = "" + _score.value = 0 + Log.i("GameViewModel", "GameViewModel created!") + resetList() + nextWord() + } + + /** + * Callback called when the ViewModel is destroyed + */ + override fun onCleared() { + super.onCleared() + Log.i("GameViewModel", "GameViewModel destroyed!") + } + + /** Methods for updating the UI **/ + fun onSkip() { + if (!wordList.isEmpty()) { + _score.value = (_score.value)?.minus(1) + } + nextWord() + } + fun onCorrect() { + if (!wordList.isEmpty()) { + _score.value = (_score.value)?.plus(1) + } + nextWord() + } + + /** + * Moves to the next _word in the list. + */ + private fun nextWord() { + if (wordList.isEmpty()) { + onGameFinish() + + } else { + //Select and remove a _word from the list + _word.value = wordList.removeAt(0) + } + } + + + + /** Method for the game completed event **/ + + fun onGameFinishComplete() { + _eventGameFinish.value = false + } + + fun onGameFinish() { + _eventGameFinish.value = true + } + +} diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt new file mode 100755 index 0000000..952ebbd --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword.screens.score + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.navigation.fragment.findNavController +import com.example.android.guesstheword.R +import com.example.android.guesstheword.databinding.ScoreFragmentBinding + +/** + * Fragment where the final score is shown, after the game is over + */ +class ScoreFragment : Fragment() { + + private lateinit var viewModel: ScoreViewModel + private lateinit var viewModelFactory: ScoreViewModelFactory + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + // Inflate view and obtain an instance of the binding class. + val binding: ScoreFragmentBinding = DataBindingUtil.inflate( + inflater, + R.layout.score_fragment, + container, + false + ) + + viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score) + viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(ScoreViewModel::class.java) + binding.scoreViewModel = viewModel + + // Specify the current activity as the lifecycle owner of the binding. + // This is used so that the binding can observe LiveData updates + binding.lifecycleOwner = this + + // Navigates back to game when button is pressed + viewModel.eventPlayAgain.observe(this, Observer { playAgain -> + if (playAgain) { + findNavController().navigate(ScoreFragmentDirections.actionRestart()) + viewModel.onPlayAgainComplete() + } + }) + + return binding.root + } +} diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt new file mode 100644 index 0000000..a47a91e --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword.screens.score + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** + * ViewModel for the final screen showing the score + */ +class ScoreViewModel(finalScore: Int) : ViewModel() { + + private val _score = MutableLiveData() + val score: LiveData + get() = _score + + private val _eventPlayAgain = MutableLiveData() + val eventPlayAgain: LiveData + get() = _eventPlayAgain + + + init { + _score.value = finalScore + } + + fun onPlayAgain() { + _eventPlayAgain.value = true + } + + fun onPlayAgainComplete() { + _eventPlayAgain.value = false + } + + +} diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt new file mode 100644 index 0000000..1671608 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword.screens.score + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class ScoreViewModelFactory (private val finalScore: Int) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) { + return ScoreViewModel(finalScore) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } + +} diff --git a/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/title/TitleFragment.kt b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/title/TitleFragment.kt new file mode 100755 index 0000000..9fa0a68 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/java/com/example/android/guesstheword/screens/title/TitleFragment.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.guesstheword.screens.title + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.example.android.guesstheword.R +import com.example.android.guesstheword.databinding.TitleFragmentBinding + +/** + * Fragment for the starting or title screen of the app + */ +class TitleFragment : Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + // Inflate the layout for this fragment + val binding: TitleFragmentBinding = DataBindingUtil.inflate( + inflater, R.layout.title_fragment, container, false) + + binding.playGameButton.setOnClickListener { + findNavController().navigate(TitleFragmentDirections.actionTitleToGame()) + } + return binding.root + } +} diff --git a/GuessTheWordDataBinding/app/src/main/res/anim/slide_in_right.xml b/GuessTheWordDataBinding/app/src/main/res/anim/slide_in_right.xml new file mode 100755 index 0000000..dc03344 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/GuessTheWordDataBinding/app/src/main/res/anim/slide_out_left.xml b/GuessTheWordDataBinding/app/src/main/res/anim/slide_out_left.xml new file mode 100755 index 0000000..ec2c114 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/GuessTheWordDataBinding/app/src/main/res/drawable/ic_launcher_background.xml b/GuessTheWordDataBinding/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100755 index 0000000..cf04c59 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/GuessTheWordDataBinding/app/src/main/res/drawable/ic_launcher_foreground.xml b/GuessTheWordDataBinding/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100755 index 0000000..1b3e6a7 --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff --git a/GuessTheWordDataBinding/app/src/main/res/layout/game_fragment.xml b/GuessTheWordDataBinding/app/src/main/res/layout/game_fragment.xml new file mode 100755 index 0000000..dd4731e --- /dev/null +++ b/GuessTheWordDataBinding/app/src/main/res/layout/game_fragment.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + +