Part ONE < ===
第一部分 <===
The price of the Stock Exchange is depending on News. On the front page, you shall display the news for the app user. At this time, I choose RapidAPI, which has some free stock APIs. You can choose one as your additional stock news activity. I will divide one activity into three activities:
证券交易所的价格取决于新闻。 在首页上,您应为应用程序用户显示新闻。 目前,我选择RapidAPI ,它具有一些免费的库存API。 您可以选择一个作为其他股票新闻活动。 我将一个活动分为三个活动:
MainActivity => navigator
MainActivity =>导航器
TiingoActivity => Tiingo server
TiingoActivity => Tiingo服务器
BloombergActivity => Bloomberg server
BloombergActivity =>彭博服务器
-=== MenU ===- (— ===MenU=== —)
💲1. 注册一个免费帐户
💣2。 卷曲测试
🍨3。 RestConfig和Story数据类
⌘4。 网络:拆分API服务
5英镑 拆分ApiModule:两个Api服务
🐙6。 UI:MainActivity,TiingoActivity和BloombergActivity
7英镑 测试连接和活动 (💲 1. Register A Free Account
💣2. Curl Test
🍨3. RestConfig and Story Data Class
⌘4. Network: Split the API service
🔪5. Split ApiModule: Two Api Services
🐙6. UI: MainActivity, TiingoActivity, and BloombergActivity
📳7. Test Connection and Activities)
💲1。 注册免费帐户 (💲1. Register A Free Account)
=> Menu
=> 菜单
I choose Bloomberg as my stock news API. It’s free if the requests are less than 500/month or 16/day. That is a great tool for the developer who wants to develop tools on the Stock Exchange.
我选择Bloomberg作为我的股票新闻API。 如果请求数少于500 /月或16 /天,则免费。 对于想要在联交所开发工具的开发人员来说,这是一个很好的工具。
RapidAPI also offers us some sample programming code. I am working on Android. So Java is the best fit for me.
RapidAPI还为我们提供了一些示例编程代码。 我正在使用Android。 因此Java最适合我。
After registration, we can start to work on the App.
注册后,我们可以开始在该应用程序上工作。
💣2。 卷曲测试 (💣2. Curl Test)
=> Menu
=> 菜单
Open a terminal, such as CMD; let’s use the code from Java.
打开一个终端,例如CMD; 让我们使用Java中的代码。
curl -v "https://bloomberg-market-and-financial-news.p.rapidapi.com/stories/list?template=CURRENCY&id=usdjpy" ^
-H "x-rapidapi-host: bloomberg-market-and-financial-news.p.rapidapi.com" ^
-H "x-rapidapi-key:YOUR API KEY"
It returns a JSON response.
它返回一个JSON响应。
>curl -v ...
*...
<
{...JSON CONTENT...}* Connection #0 to host bloomberg-market-and-financial-news.p.rapidapi.com left intact>
This is a good one.
这个不错。
🍨3。 RestConfig和Story数据类 (🍨3. RestConfig and Story Data Class)
=> Menu
=> 菜单
Add Key and Server
添加密钥和服务器
The “secret” will move into the “network” package.
“ 秘密 ”将移至“ 网络 ”包中。
RestConfig.kt: Two sets of API server data: Tiingo and Bloomberg.
RestConfig.kt :两组API服务器数据: Tiingo和Bloomberg 。
class RestConfig {
companion object {
const val DEBUG = true
// Tiingo
const val Tiingo_TEST_END = "/api/test"
const val Tiingo_TOKEN = "Your Token"
const val Tiingo_SERVER = "https://api.tiingo.com"
const val Tiingo_Header1 = "Content-Type: application/json"
const val Tiingo_Header2 = "Authorization: Token $Tiingo_TOKEN"// Bloomberg
const val Bloomberg_STORY_CURRENCY = "/stories/list?template=CURRENCY&id=usdjpy"
const val Bloomberg_SERVER = "https://bloomberg-market-and-financial-news.p.rapidapi.com"
const val Bloomberg_KEY = "Your Key"
const val Bloomberg_Header1 = "x-rapidapi-host: bloomberg-market-and-financial-news.p.rapidapi.com"
const val Bloomberg_Header2 = "x-rapidapi-key: $Bloomberg_KEY"
}
}
Split Data Class
分割资料类别
At the “data” package, I create a “tiingo” package to store Message class, and I create a “bloomberg” package to store the new data.
在“数据”包中,我创建一个“ tiingo ”包来存储Message类,并创建一个“ Bloomberg ”包来存储新数据。
JSON:
JSON:
{"stories":[...}
At data/bloomberg, the response message is “stories”. So I copy the JSON code into JK plugin to generate the Story class.
在data / bloomberg,响应消息为“故事”。 因此,我将JSON代码复制到JK插件中以生成Story类。
Story.kt,
Story.kt ,
data class Story(
val stories: List<StoryX>,
val title: String
)
StoryX.kt,
StoryX.kt ,
data class StoryX(
val card: String,
val internalID: String,
val longURL: String,
val primarySite: String,
val published: Int,
val resourceType: String,
val shortURL: String,
val thumbnailImage: String,
val title: String
)
We will insert StoryX data on a RecyclerView.
我们将StoryX数据插入RecyclerView 。
⌘4。 网络:拆分API服务 (⌘4. Network: Split the API service)
=> Menu
=> 菜单
I have two API services in my app. So I split them into two packages.
我的应用程序中有两个API服务。 所以我将它们分成两个包。
At network/tiingo/:
在网络/ tiingo /:
TiingoApiService.kt,
TiingoApiService.kt,
interface TiingoApiService {
// Tiingo Test Message
@Headers(
RestConfig.Tiingo_Header1,
RestConfig.Tiingo_Header2
)
@GET(RestConfig.Tiingo_TEST_END)
suspend fun getTestMessage(): Message
}
TiingoApiServiceHelper.kt,
TiingoApiServiceHelper.kt ,
interface TiingoApiServiceHelper {
suspend fun getTestMsg(): Message
}
TiingoApiServiceHelperImpl.kt
TiingoApiServiceHelperImpl.kt
@Singleton
class TiingoApiServiceHelperImpl @Inject constructor(
private val tiingoApiService: TiingoApiService
) : TiingoApiServiceHelper {
override suspend fun getTestMsg():
Message = tiingoApiService.getTestMessage()
}
At network/bloomberg:
在网络/彭博社 :
BloombergApiService.kt,
BloombergApiService.kt ,
interface BloombergApiService {
// Bloomberg Stories: Currency
@Headers(
RestConfig.Bloomberg_Header1,
RestConfig.Bloomberg_Header2
)
@GET(RestConfig.Bloomberg_STORY_CURRENCY)
suspend fun getStory(): Story
}
BloombergApiServiceHelper.kt,
BloombergApiServiceHelper.kt ,
interface BloombergApiServiceHelper {
// Bloomberg Stories: Currency
suspend fun getBloombergStory(): Story
}
BloombergApiServiceHelperImpl.kt,
BloombergApiServiceHelperImpl.kt ,
@Singleton
class BloombergApiServiceHelperImpl @Inject constructor(
private val bloombergApiService: BloombergApiService
) : BloombergApiServiceHelper {
// Bloomberg Stories: Currency
override suspend fun getBloombergStory():
Story = bloombergApiService.getStory()
}
5英镑 拆分ApiModule:两个Api服务 (🔪5. Split ApiModule: Two Api Services)
=> Menu
=> 菜单
I split AppModule into two modules: BloombergNetworkModule and TiingoNetworkModule.
我将AppModule分为两个模块: BloombergNetworkModule和TiingoNetworkModul e。
Qualifier: Tag Your Provides
限定词:标记您的提供
Also, I create two qualifiers to separate the API services. At dagger/qualifier:
另外,我创建了两个限定符来分隔API服务。 在匕首/限定符处 :
TypeEnum.kt, it contains all of the types of API service.
TypeEnum.kt ,它包含所有API服务类型。
enum class TypeEnum {URL, SERVER, GSON, OKHTTP, RETROFIT, APISERVICE, APIHELPER}
BloombergNetwork.kt,
彭博网络
@Qualifier
@Documented
@Retention(AnnotationRetention.RUNTIME)
annotation class BloombergNetwork(val value: TypeEnum)
TiingoNetwork.kt,
TiingoNetwork.kt ,
@Qualifier
@Documented
@Retention(AnnotationRetention.RUNTIME)
annotation class TiingoNetwork(val value: TypeEnum)
👩🏻ApiModule: 👶🏻BloombergNetworkModule 👶🏻TiingoNetworkModule
piApiModule:👶🏻BloombergNetworkModule👶🏻TiingoNetworkModule
BloombergNetworkModule.kt,
彭博网络模块.kt ,
@Module
@InstallIn(ApplicationComponent::class)
class BloombergNetworkModule {
@Provides @BloombergNetwork(URL)
fun provideBloombergUrl() = RestConfig.Bloomberg_SERVER @Provides @BloombergNetwork(GSON)
fun provideGson(): Gson = GsonBuilder().setLenient().create() @Provides @BloombergNetwork(OKHTTP)
fun provideOkHttpClient() =
if (RestConfig.DEBUG) { // debug ON
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODYOkHttpClient.Builder()
.addInterceptor(logger)
.readTimeout(100, TimeUnit.SECONDS)
.connectTimeout(100, TimeUnit.SECONDS)
.build()
} else // debug OFF
OkHttpClient.Builder()
.readTimeout(100, TimeUnit.SECONDS)
.connectTimeout(100, TimeUnit.SECONDS)
.build() @Provides @BloombergNetwork(RETROFIT)
fun provideRetrofit(@BloombergNetwork(OKHTTP) okHttpClient: OkHttpClient,@BloombergNetwork(URL) BaseURL: String):
Retrofit = Retrofit.Builder()
.baseUrl(BaseURL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build() @Provides @BloombergNetwork(APISERVICE)
fun provideBloombergApiService(@BloombergNetwork(RETROFIT) retrofit: Retrofit):
BloombergApiService = retrofit.create(BloombergApiService::class.java) @Provides @BloombergNetwork(APIHELPER)
fun provideBloomberApiServiceHelper(apiHelper: BloombergApiServiceHelperImpl):
BloombergApiServiceHelper = apiHelper}
Please check the Bold words, it’s easy to understand qualifier. Let’s tag BloombergApiServiceHelperImpl.kt, at network/bloombBloomberg, to create the binding.
请检查粗体字,这很容易理解限定词。 让我们在network / bloombBloomberg标记BloombergApiServiceHelperImpl.kt来创建绑定。
class BloombergApiServiceHelperImpl @Inject constructor(@BloombergNetwork(APISERVICE)
private val bloombergApiService: BloombergApiService
) : BloombergApiServiceHelper {...
Use Hilt anchors to check the binding until you hit the URL tag. Next,
使用Hilt锚点检查绑定,直到您单击URL标记。 下一个,
TiingoNetworkModule.kt,
TiingoNetworkModule.kt ,
@Module
@InstallIn(ApplicationComponent::class)
class TiingoNetworkModule {
@Provides @TiingoNetwork(URL)
fun provideBaseUrl() = RestConfig.Tiingo_SERVER @Provides @TiingoNetwork(GSON)
fun provideGson(): Gson = GsonBuilder().setLenient().create() @Provides @TiingoNetwork(OKHTTP)
fun provideOkHttpClient() =
if (RestConfig.DEBUG) { // debug ON
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODYOkHttpClient.Builder()
.addInterceptor(logger)
.readTimeout(100, TimeUnit.SECONDS)
.connectTimeout(100, TimeUnit.SECONDS)
.build()
} else // debug OFF
OkHttpClient.Builder()
.readTimeout(100, TimeUnit.SECONDS)
.connectTimeout(100, TimeUnit.SECONDS)
.build() @Provides @TiingoNetwork(RETROFIT)
fun provideRetrofit(@TiingoNetwork(OKHTTP) okHttpClient: OkHttpClient, @TiingoNetwork(URL) BaseURL: String
): Retrofit = Retrofit.Builder()
.baseUrl(BaseURL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build() @Provides @TiingoNetwork(APISERVICE)
fun provideApiService(@TiingoNetwork(RETROFIT) retrofit: Retrofit
): TiingoApiService = retrofit.create(TiingoApiService::class.java) @Provides @TiingoNetwork(APIHELPER)
fun provideApiServiceHelper(apiHelper: TiingoApiServiceHelperImpl):
TiingoApiServiceHelper = apiHelper}
Let’s fix the TiingoApiServiceHelperImpl.kt, at network/tiingo.
让我们来解决的TiingoApiServiceHelperImpl.kt,在网络/ tiingo。
class TiingoApiServiceHelperImpl @Inject constructor(@TiingoNetwork(APISERVICE) private val tiingoApiService...
🐙6。 UI:MainActivity,TiingoActivity和BloombergActivity (🐙6. UI: MainActivity, TiingoActivity, and BloombergActivity)
=> Menu
=> 菜单
The MainActivity has no longer to display the Tiingo’s content. Now, it acts as a navigator to show sites that users can visit.
MainActivity不再显示Tiingo的内容。 现在,它可以充当导航器来显示用户可以访问的站点。
AndroidManifest.xml,
AndroidManifest.xml ,
<application
...>
<activity android:name=".view.bloomberg.BloombergNewsActivity"></activity>
<activity android:name=".view.tiingo.TiingoActivity"></activity>
<activity android:name=".view.main.MainActivity">
It’s MVVM, so I place all the UI into the “view” package.
它是MVVM,因此我将所有UI都放在“ view ”包中。
MainActivity
主要活动
The activity_main.xml,
activity_main.xml ,
That’s it: two buttons.
就是这样:两个按钮。
MainActivity.kt,
MainActivity.kt ,
private const val REQUEST_CODE_PERMISSIONS = 1111
private val REQUIRED_PERMISSIONS = arrayOf(...)
@AndroidEntryPoint // Hilt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)setupButton()
}
fun setupButton() {
lgd("Setup buttons")
tiingo_bt.setOnClickListener {val mIntent = Intent(this,
TiingoActivity::class.java)
startActivity(mIntent)}bloomberg_bt.setOnClickListener {val mIntent = Intent(this,
BloombergNewsActivity::class.java)
startActivity(mIntent)}}override fun onRequestPermissionsResult(...) {...}private fun allPermissionsGranted()...companion object {
private val TAG = "MYLOG MainActivity"
fun lgd(s: String) = Log.d(TAG, s)
// len: 0=>short; 1=>long
fun msg(context: Context, s: String, len: Int) =
if (len == 0)
Toast.makeText(context, s,
Toast.LENGTH_SHORT).show()
else
Toast.makeText(context, s,
Toast.LENGTH_LONG).show()
const val INTERNET = "INTERNET"
}
}
TiingoActivity
Tiingo活动
The activity_tiingo.xml,
activity_tiingo.xml,
TiingoActivity.kt, it is as same as old MainActivity. Please rename the ViewModel to TiingoViewModel.
TiingoActivity.kt ,它与旧的MainActivity相同。 请将ViewModel重命名为TiingoViewModel 。
@AndroidEntryPoint // Hilt
class TiingoActivity : AppCompatActivity() {
private val tiingoViewModel : TiingoViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tiingo)setupObserver()
}
fun setupObserver() {
lgd("setup observer")
tiingoViewModel.message.observe(this, Observer {...})
}
companion object {
private val TAG = "MYLOG TiingoAct"
fun lgd(s: String) = Log.d(TAG, s)
// len: 0=>short; 1=>long
fun msg(context: Context, s: String, len: Int) =
if (len == 0)
Toast.makeText(context, s,
Toast.LENGTH_SHORT).show()
else
Toast.makeText(context, s,
Toast.LENGTH_LONG).show()
const val INTERNET = "INTERNET"
}
}
TiingoViewModel.kt, it is as same as old MainViewModel. Please rename the Repository to TiingoRepository.
TiingoViewModel.kt ,它与旧的MainViewModel相同。 请将存储库重命名为TiingoRepository。
class TiingoViewModel @ViewModelInject constructor(
private val tiingoRepository: TiingoRepository,
networkHelper: NetworkHelper
) : ViewModel() {...
TiingoRepository.kt, at the “room” package.
TiingoRepository.kt ,位于“ room ”包中。
@Singleton
class TiingoRepository @Inject constructor(@TiingoNetwork(APIHELPER)
private val tiingoTiingoApiHelper: TiingoApiServiceHelper
) {
suspend fun getTestMsg():
Message = tiingoTiingoApiHelper.getTestMsg()
}
Please check the Hilt link. You must be able to jump back to TiingoNetworkModule by clicking 🔨,
请检查“链接”链接。 你必须能够通过单击跳回TiingoNetworkModule🔨,
BloombergActivity
彭博活动
The activiy_bloomberg_news.html,
activiy_bloomberg_news.html ,
BloombergNewsActivity.kt,
彭博新闻活动
@AndroidEntryPoint // Hilt
class BloombergNewsActivity : AppCompatActivity() {
private val newsViewModel : BloombergNewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bloomberg_news)setupObserver()
}
fun setupObserver() {
newsViewModel.story.observe(this, Observer {when (it.status) {
Status.SUCCESS -> {
news_tv.text = it.data!!.stories.toString()
news_pb.visibility = View.GONE}
Status.LOADING -> {
news_pb.visibility = View.VISIBLE}
Status.ERROR -> {
news_pb.visibility = View.GONEval tempStr = it.message
// internet error
val foundIdx = tempStr?.indexOf(
INTERNET, 0)!!
if (foundIdx > 0) {
val spStr = SpannableString(tempStr)
val color = Color.REDspStr.setSpan(
ForegroundColorSpan(color),
foundIdx,
foundIdx+8,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
news_tv.text = spStr
} else { // other error
news_tv.text = tempStr
}
msg(this, tempStr, 1)
}
}})
} companion object {
private val TAG = "MYLOG BloombergAct"
fun lgd(s: String) = Log.d(TAG, s)
fun lge(s: String) = Log.e(TAG, s)
fun lgi(s: String) = Log.i(TAG, s)
// len: 0=>short; 1=>long
fun msg(context: Context, s: String, len: Int) =
if (len == 0)
Toast.makeText(context, s,
Toast.LENGTH_SHORT).show()
else
Toast.makeText(context, s,
Toast.LENGTH_LONG).show()
const val INTERNET = "INTERNET"
}
}
BloombergNewsViewModel.kt,
彭博新闻视图模型.kt ,
class BloombergNewsViewModel @ViewModelInject constructor(
private val bloombergRepository: BloombergRepository,
networkHelper: NetworkHelper
) : ViewModel() {
// cached
private val _story = MutableLiveData<Resource<Story>>()
// public
val story: LiveData<Resource<Story>> = _story
init {
_story.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
fetchStory()
} else {
_story.postValue(
Resource.error(
data = null,
message = internetErr))
}
}
fun fetchStory() {viewModelScope.launch {lgd("fetching story...")
try {
_story.value = Resource.success(
data = bloombergRepository.getCurrencyStory())
} catch (exception: Exception) {
_story.value = Resource.error(
data = null,
message = exception.message ?: otherErr
)
}}}
companion object {
private val TAG = "MYLOG BB-NewsVM"
const val otherErr = "Error Occurred!"
const val internetErr = "Internet Connection Error"
fun lgd(s: String) = Log.d(TAG, s)
}
}
BloombergRepository.kt, at the “room” package.
BloombergRepository.kt ,位于“ 房间 ”包中。
@Singleton
class BloombergRepository @Inject constructor(@BloombergNetwork(APIHELPER)
private val bloombergApiHelper: BloombergApiServiceHelper
) {
suspend fun getCurrencyStory():
Story = bloombergApiHelper.getBloombergStory()
}
翻译自: https://medium.com/@homanhuang/android-stock-app-2-dagger-hilt-with-multiple-retrofits-cc13472287dd