How to get all GitHub issues using GraphQL
Issue #393
https://developer.github.com/v4/explorer/
1 | query { |
In node.js
1 | const GraphQL = require('graphql-request') |
Issue #393
https://developer.github.com/v4/explorer/
1 | query { |
In node.js
1 | const GraphQL = require('graphql-request') |
Issue #392
It’s been a long journey since https://github.com/onmyway133/blog/issues/1, next step is to keep GitHub issues as source, and mirror those to a static site.
Use 2 repos
1 | npm install -g hexo-cli |
Update _config.yml
1 | deploy: |
1 | hexo clean |
Issue #391
People who make fun of Javascript probably don’t understand implicit type coersion and when to use triple equal. Javascript is very unexpected, but when we work with this language, we need to be aware.
- Coercion–Automatically changing a value from one type to another.
- If x is Number and y is String, return x == ToNumber(y)
- If x is String or Number and y is Object, return x == ToPrimitive(y)
- Empty array becomes empty string
Issue #390
An object ‘s property can be null or undefined.
Accessing step by step is tedious
1 | props.user && |
Dynamic parsing path is too clever and involves string in the end, which is a no no
1 | const get = (p, o) => |
Instead let’s use function and catch errors explicitly, and defaults with a fallback
1 | const get: (f, defaultValue) => { |
Issue #389
Prefer flow over TypeScript for simplicity
Javascript primitive types number
and string
are too general and do not express the domain objects. Because lack of type alias in Javascript, we can use flow
1 | export type Centimeter = number |
Issue #388
https://kotlinlang.org/docs/reference/coroutines/flow.html
Using List
result type we can only return all the values at once. To represent the stream of values that are being asynchronously computed we can use Flow type similarly to the Sequence type for synchronously computed values:
Issue #387
Google Analytics is shutting down. From Firebase Analytics console, we can choose to upgrade to Google Analytics, no code change is needed.
https://support.google.com/firebase/answer/9167112?hl=en
In October 2019, we will start to sunset Google Analytics mobile-apps reporting based on the Google Analytics Services SDKs for Android and iOS.
https://firebase.googleblog.com/2019/07/firebase-google-analytics-upgrade.html
Thanks to our continued partnership with Google Analytics, you can now upgrade your Firebase projects to the next generation of app analytics!
https://www.e-nor.com/blog/google-analytics/google-analytics-unifies-app-and-website-measurement
Google Analytics team has officially launched a new type of GA properties called “App + Web” to open public beta
It is a new GA property type that allows you to combine app and web data for unified reporting and analysis
Over the coming weeks, those who have existing Firebase projects will be able to upgrade your projects to the next generation Google Analytics experience as follows:
Issue #386
When it comes to right
and bottom
side, we should use negative values, and use lessThan
, as it means less than a negative value
Issue #385
1 | + (BOOL)isJailbroken { |
Issue #383
Right click res -> New -> Android Resource Directory
, select anim
and name it anim
Right click res/anim -> New -> Android Resource file
, name it bounce
1 |
|
Issue #382
1 | import android.content.Context |
1 | val set = ConstraintSet() |
Issue #381
From API < 17, there is ViewCompat.generateViewId()
For API 17, there is View.generateViewId()
Note that to use ConstraintSet
, all views under ConstraintLayout
inside xml must have unique id
1 | val imageView = ImageView(context) |
Issue #380
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
Android 8.0 (API level 26) and Android Support Library 26 introduce support for APIs to request fonts from a provider application instead of bundling files into the APK or letting the APK download fonts. The feature is available on devices running Android API versions 14 and higher through the Support Library 26
File -> New -> Folder -> Assets Folder
to create src/main/assets/fonts
1 | al myTypeface = Typeface.createFromAsset(assets, "fonts/myFont.ttf") |
Create font directory
Right click res -> New -> Android Resource Directory
, select font
and name the folder font
Add custom fonts to res/font
folder. Note that name must be lower case and underscore, like opensans_extrabolditalic.ttf
Right click res/font -> New -> Font resource file
to create font family
opensans.xml
1 |
|
Issue #379
1 | interface Api { |
1 | class Repo { |
1 | class ViewModel(val repo: Repo): ViewModel() { |
The above run in serial. To run in parallel, we can use async
1 | import kotlinx.coroutines.async |
https://medium.com/@elizarov/structured-concurrency-722d765aa952
With structured concurrency async coroutine builder became an extension on CoroutineScope just like launch did. You cannot simply write async { … } anymore, you have to provide a scope. A proper example of parallel decomposition becomes:
https://proandroiddev.com/part-2-coroutine-cancellation-and-structured-concurrency-2dbc6583c07d
coroutineScope function can be used to create a custom scope that suspends and only completes when all coroutines launched within that scope complete. If any of the children coroutines within the coroutineScope throws an exception, all other running sibling coroutines gets cancelled and this exception is propagated up the hierarchy. If the parent coroutine at the top of the hierarchy does not handle this error, it will also be cancelled.
Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values when all deferred computations are complete or resumes with the first thrown exception if any of computations complete exceptionally including cancellation.
This function is not equivalent to deferreds.map { it.await() } which fails only when it sequentially gets to wait for the failing deferred, while this awaitAll fails immediately as soon as any of the deferreds fail.
This suspending function is cancellable. If the Job of the current coroutine is cancelled or completed while this suspending function is waiting, this function immediately resumes with CancellationException.
Issue #378
After having a generic RecyclerView, if we want to show multiple kinds of data in Fragment, we can use generic.
We may be tempted to use interface or protocol, but should prefer generic.
1 | class FeedFragment() : Fragment() { |
The difference between each kind are
Here we also use lifecycleScope
from lifecycle runtime ktx
1 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01" |
1 | private fun <T> handle( |
Then we just need to provide the required data
1 | private fun handleDev() { |
Issue #377
OneSignal is an alternative for Parse for push notifications but the sdk has many extra stuff and assumptions and lots of swizzling.
We can just use Rest to make API calls. From https://github.com/onmyway133/Dust
Every official push notification SDK can do many things
OneSignal
1 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { |
Here is the implementation
1 | import UIKit |
1 | import Foundation |
1 | import Foundation |
Issue #376
https://github.com/onmyway133/Omnia/blob/master/Sources/Shared/Debouncer.swift
1 | import Foundation |
1 | import XCTest |
Issue #375
1 | final class LifecyclerHandler { |
1 | private let lifecycleHandler = LifecyclerHandler() |
Issue #374
Add accessibilityIdentifier
to the parent view of GMSMapView
. Setting directly onto GMSMapView
has no effect
1 | accessibilityIdentifier = "MapView" |
1 | let map = app.otherElements.matching(identifier: "MapView").element(boundBy: 0) |
Need to enable accessibility
1 | mapView.accessibilityElementsHidden = false |
Can’t use pinch to zoom out with UITests, so need to mock location !!!
1 | map().pinch(withScale: 0.05, velocity: -1) |
Need to use gpx to mock to preferred location
1 | let map = app.otherElements[Constant.AccessibilityId.mapView.rawValue] |
Try isAccessibilityElement = true
for PinView
, can’t touch!!
Use coordinate, can’t touch !!
1 | let coordinate = pin.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) |
Try traversing all the pins, can’t touch
1 | Array(0..<pins.count).forEach { |
When po app.otherElements
, coordinates are outside screen
1 | Other, {{1624.0, 1624.0}, {30.0, 30.0}}, identifier: 'pin', label: 'Hello world' |
My PinView
has isHittable
being false, no matter how I use coordinate or enable it. It can’t be touched.
Go to Xcode -> Open Developer Tool -> Accessibility Inspector to inspect our app in iOS simulator
It turns out that if I do
1 | po app.buttons |
It shows all the GMSMarker, but with identifier
having class name MyApp.MyStopMarker
, so just need to use buttons
1 | extension NSPredicate { |
Updated at 2021-01-26 09:47:41
Issue #373
UIButton.contentEdgeInsets
does not play well with Auto Layout, we need to use intrinsicContentSize
1 | final class InsetButton: UIButton { |
Issue #371
Scrolling UIScrollView
is used in common scenarios like steps, onboarding.
From iOS 11, UIScrollView has contentLayoutGuide
and frameLayoutGuide
https://developer.apple.com/documentation/uikit/uiscrollview/2865870-contentlayoutguide
Use this layout guide when you want to create Auto Layout constraints related to the content area of a scroll view.
https://developer.apple.com/documentation/uikit/uiscrollview/2865772-framelayoutguide
Use this layout guide when you want to create Auto Layout constraints that explicitly involve the frame rectangle of the scroll view itself, as opposed to its content rectangle.
I found out that using contentLayoutGuide
and frameLayoutGuide
does not work in iOS 11, when swiping to the next page, it breaks the constraints. iOS 12 works well, so we have to check iOS version
Let the contentView
drives the contentSize
of scrollView
1 | import UIKit |
1 | extension UILayoutGuide { |
Issue #370
Define response model
1 | import com.squareup.moshi.Json |
Here is the query
1 | { |
Here’s how request looks in Insomnia
1 | > POST /v2/api/graphql HTTP/1.1 |
To post as json, need to use object for Moshi to convert
1 | data class GetTopBody( |
And consume it in ViewModel. Use multiline string interpolation. No need to set contentLength
1 | class ViewModel(val repo: Repo): ViewModel() { |
The response looks like
1 | { |
Instead of using an object, we can use Map
. If using HashMap, I get
Unable to create @Body converter for java.util.HashMap<java.lang.String, java.lang.String>
1 |
|
Use Network Profiler to inspect failure View > Tool Windows > Profiler
Issue #369
translatesAutoresizingMaskIntoConstraints
set to false
UIStackView
stackview.alignment = .center
if you see UIStackView
trying to set same trailing or leading edges for its subviewsUIStackView
1 | imageView.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: .vertical) |
https://stackoverflow.com/a/58666480/1418457
Issue #368
See https://github.com/onmyway133/Omnia/blob/master/Sources/iOS/NSLayoutConstraint.swift
1 | extension NSLayoutConstraint { |
1 | extension UILayoutGuide { |
1 | extension UIView { |
Issue #367
1 | https://api.github.com/search/repositories?sort=stars&order=desc&q=language:javascript,java,swift,kotlin&q=created:>2019-08-21 |
1 | interface Api { |
1 |
|
1 | class ViewModel(val repo: Repo, val dateProvider: DateProvider): ViewModel() { |
Issue #366
Code uses Retrofit 2.6.0 which has Coroutine support
app/build.gradle
1 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01" |
Api.kt
1 | import retrofit2.http.GET |
Repo.kt
1 | import retrofit2.Retrofit |
ViewModel.kt
1 | import androidx.lifecycle.ViewModel |
Article.kt
1 | import com.squareup.moshi.Json |
Issue #365
1 | import WebKit |
Issue #364
AppFlowController.swift
1 | import UIKit |
AppDelegate.swift
1 |
|
Issue #363
https://medium.com/androiddevelopers/viewmodels-a-simple-example-ed5ac416317e
Rotating a device is one of a few configuration changes that an app can go through during its lifetime, including keyboard availability and changing the device’s language. All of these configuration changes cause the Activity to be torn down and recreated
The ViewModel class is designed to hold and manage UI-related data in a life-cycle conscious way. This allows data to survive configuration changes such as screen rotations.
The ViewModel exists from when the you first request a ViewModel (usually in the onCreate the Activity) until the Activity is finished and destroyed. onCreate may be called several times during the life of an Activity, such as when the app is rotated, but the ViewModel survives throughout.
Storing an Application context in a ViewModel is okay because an Application context is tied to the Application lifecycle. This is different from an Activity context, which is tied to the Activity lifecycle. In fact, if you need an Application context, you should extend AndroidViewModel which is simply a ViewModel that includes an Application reference.
The first time the ViewModelProviders.of method is called by MainActivity, it creates a new ViewModel instance. When this method is called again, which happens whenever onCreate is called, it will return the pre-existing ViewModel associated with the specific Court-Counter MainActivity
1 | ViewModelProviders.of(<THIS ARGUMENT>).get(ScoreViewModel.class); |
This allows you to have an app that opens a lot of different instances of the same Activity or Fragment, but with different ViewModel information
1 | /** |
It seems like ViewModelProviders.of is just a factory of ViewModelProvider, which depends upon ViewModelFactory and a ViewModelStore.
1 | MyViewModelFactory factory = new MyViewModelFactory(data1, data2); |
1 | public class ViewModelStore { |
HolderFragment is a regular Android Headless Fragment. It is the scope where all ViewModels inside the ViewModelStore will live.
1 |
|
1 | static class HolderFragmentManager { |
The HolderFragment has an inner static class named HolderFragmentManager. The HolderFragmentManager creates and manages HolderFragment instances.
After creating the instances it associates them with an Activity or Fragment.
The whole process is done using the methods holderFragmentFor(Activity) and holderFragmentFor(Fragment).
By setting retain instance to true and not providing a view, the HolderFragment becomes essentially a headless Fragment that is retained for as long as the Activity is not destroyed.
1 | public HolderFragment() { |
void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
1 | get(MyViewModel.class) |
It tries to retrieve a MyViewModel instance from the store. If none is there, it uses the factory to create it and then stores it into HashMap<String, ViewModel>. In order to retrieve the already created ViewModel, it generates a key from the class qualified name.
Issue #362
1 | let tapGR = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) |
We need to use lazy
instead of let
for gesture to work
1 | lazy var tapGR = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) |