How to repeat a Litho animation - litho

The Litho animation examples all begin when a user triggers an event. But I need an animation that begins right away and continues indefinitely. In other words, I have the same problem as How to run a Litho animation automatically? but I need a solution for Litho animations as opposed to basic Android animations.
Note, I asked a related question How to run a Litho animation automatically? when I tried to modify one of Litho's examples to initiate the animation without a user event. But the question I'm asking now is how to repeat the animation once it's started?

To start a Litho animation automatically and repeat it indefinitely, I modified RTAnimationComponentSpec by starting a TimerTask:
#OnCreateInitialState
static void createInitialState(
ComponentContext c) {
startRepeatingAnimation(c);
}
static void startRepeatingAnimation(final ComponentContext c) {
Log.e(TAG, "Repeat animation handler: about to scheduleAtFixedRate");
TimerTask animateRepeat = new java.util.TimerTask() {
public void run() {
try {
Log.e(TAG, "Repeat animation handler: about to updateStateAsync");
RTAnimationComponent.updateStateAsync(c);
} catch (Exception e) {
Log.e(TAG, "Repeat animation handler: exception while animating: [" + e + "]");
}
}
};
new java.util.Timer().scheduleAtFixedRate(animateRepeat, 0, FADE_IN_OUT_DURATION + FADE_IN_DELAY + FADE_IN_STAGGER_DELAY);
}
private static final String TAG = "RTComponentSpec";
I'm not sure this is a valid use of createInitialState() though. According to the documentation, it's "To set an initial value for a state". By state, Litho means variables marked #State. Informally, though, the animation is part of the state and the TimerTask does need to be started. Semantically, initializing the TimerTask seems like it belongs to creating the initial state.
Empirically, the logs showed what I wanted. The initial log message, "Repeat animation handler: about to scheduleAtFixedRate" appears once followed by periodic instances of "Repeat animation handler: about to updateStateAsync".
I suppose the solution might also work with other Android mechanisms for scheduling work on a periodic basis

Related

Is it okay to create clicklisteners (or other listeners) inside a viewmodelscope?

I have a a fragment containing a googleMap where I am creating a bunch of Markers (which also is clickable). They are spiced with different information (colors, shapes and so on) from a room livedata query. In addition I have some MaterialButton buttons (which are styled as pushbuttons) where I toggle the Marker visible status on. At the moment, the "setup" of theese markers takes some time (200ms-2 secs, depends of amount of markers). To get out of that waiting, I was planning to use a viewmodelscope. Since there are some clicklisteners for theese buttons defined in there (they should do some action with the markers), will they still be alive when the viewmodelscope coroutine section ends, and If they are alive, do they still live in the correct coroutine-context, and do I need to do some housekeeping on the listeners when fragment and/or viewmodel ends?
I.E:
class MapsFragment:Fragment(){
private lateinit var mapsViewModel : MapsViewModel
private lateinit var googleMap : GoogleMap
//...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mapsViewModel = ViewModelProvider(requireActivity()).get(MapsViewModel::class.java)
_binding = FragmentMapsBinding.inflate(inflater, container, false)
val root:View = binding.root
//...
return root
}//onCreateView
//...
override fun onViewCreated(view: View, savedInstanceState:Bundle?){
super.onViewCreated(view, savedInstanceState)
//...
mapFragment?.getMapAsync(_googleMap->
_googleMap?.let{safeGoogleMap->
googleMap = safeGoogleMap
}?:let{
Log.e(TAG,"googleMap is null!!")
return#getMapAsync
}
//...
mapsViewModel.apply{
liveDataMapsListFromFiltered?.observe(
viewLifecycleOwner
){mapDetailList->
viewModelScope.launch{
binding.apply{
//...
siteMarkers.map{
siteMarker.remove() //removes existing markes from map on update
}
siteMarkers.clear() //empty the siteMarker array on update
//...
mapDetailList?.map{
it.apply{
//...
coordinateSiteLongitude?.let { lng->
coordinateSiteLatitude?.let { lat->
siteMarkerLatLng = LatLng(lat,lng)
siteLatLngBoundsBuilder?.include(siteMarkerLatLng)
}
}
//...
siteMarkerLatLng?.let { safeSiteMarkerLatLng ->
val siteMarkerOptions =
MarkerOptions()
.position(safeSiteMarkerLatLng)
.anchor(0.5f, 0.5f)
.visible(siteMarkerState)
.flat(true)
.title(setTicketNumber(ticketNumber?.toDouble()))
.snippet(appointmentName)//TODO: Consider build siteId instead
.icon(siteIcon[iconType])
siteMarkers.add(
googleMap.addMarker(siteMarkerOptions) //Here are the markers added
)
}//siteMarkerLatLng?.let
}//it.apply
}//mapDetailList?.map
onSiteCheckedChangeListener?.let{
fragmentMapsMapTagSelector
?.apTagSelectorMaterialButtonSite
?.removeOnCheckedChangeListener(it) //clearing listener on button before update
}
onSiteCheckedChangeListener = MaterialButton.OnCheckedChangeListener { siteButton, isChecked ->
siteMarkers.map {
it.isVisible = isChecked
}
}.also {
fragmentMapsMapTagSelector
?.mapTagSelectorMaterialButtonSite
?.addOnCheckedChangeListener(it)
}
//Will this onCheckedChangeListener still survive when this viewmodelscope runs to the end ?
}//binding.apply
}//viewModelScope.launch
}//liveDataMapsListFromFiltered.observe
}//mapsviewModel.apply
}//getMapAsync
}//onViewCreated
}//MapsFragment
I think you misunderstand what a CoroutineScope is. It determines the lifecycle of coroutines that it runs, but not of the objects created in the process of running those coroutines.
viewModelScope is a CoroutineScope that automatically cancels any coroutines it is running when the associated ViewModel is torn down. The coroutine doesn't know what you're doing with it. Cancelling a coroutine merely stops it from running to completion, like returning from a function early. In your code, you set your listeners and haven't stored references to them besides in the views they are set to, so their lives are tied to their respective view's lives.
If you were going to use a coroutine in your fragment to set up something for your UI, you would use the Fragment's lifecycleScope, not the ViewModel's viewModelScope. Like if you were fetching something to show in your UI, you would want that coroutine to be cancelled when the Fragment is destroyed, not the ViewModel which might be outliving the Fragment.
Your use of a coroutine in your example code looks pointless, because I don't see any blocking or asynchronous suspend functions being called. You mentioned setting up site markers is taking like 200ms. I'm not familiar with Google Maps since I haven't used it in the past several years, so I'm not sure which part is time-consuming. Usually, UI elements do not allow you to interact with them on background threads, so you might be out of luck. But maybe the time-consuming part is allowed to be done on background threads. You'll have to read the documentation. Using a coroutine for this won't make it take less time, but can prevent the UI from stuttering/freezing.
If you were going to do some long computation with a coroutine, you would need to switch dispatchers to do the blocking work and interact with the UI elements back on the main dispatcher. Simply putting something in a coroutine doesn't make it take less time, but it provides a convenient way to do something on another thread and then continue on the main thread after the result is ready. For example:
lifecycleScope.launchWhenStarted { // lifecycle coroutines launch on main thread by default
val result = withContext(Dispatchers.Default) { // switch to dispatcher for background work
doTimeConsumingCalculation()
}
// back on main thread:
applyResultsToMyViews(result)
}
By using launchWhenStarted instead of launch, a Fragment's lifecycleScope will pause the coroutine when the Fragment is not attached, which will prevent potential crashes from trying to update UI using requireContext() or requireActivity() when there is no Activity.

Libgdx Timer pause's on lost focus?

I noticed if I use GDXs Timer class on a default, newly created LibGDX project it will not fire at all while the application is minimized or not in focus.
At least this is true on Desktop deployment Windows 10.
JAVAs own timer class, meanwhile, fires regardless of focus.
A simple example app to demonstrate the difference;
#Override
public void create () {
//gdx timer (does not update when focus lost);
//------------------
com.badlogic.gdx.utils.Timer.schedule(new com.badlogic.gdx.utils.Timer.Task(){
#Override
public void run() {
long currentTime = System.currentTimeMillis();
Log.info("____GDX_____________________:"+currentTime+"_________");
}
}
, 0
, 1.0f );
//java timer (updates when focus lost);
//-----------------
Timer test = new Timer();
test.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
long currentTime = System.currentTimeMillis();
Log.info("___JAVA____________________:"+currentTime+"_________");
}
}, 0, 1000);
}
Running this you can clearly see both logs firing when in focus, and only Javas when not in focus.
My questions are;
a) Is the behavior of LibGDXs timer expected, or have I done something wrong in setup? The description of LibGDXs timer doesn't seem to mention the auto-pause.
b) I wish my application to run in the background unless explicitly paused. Merely,say, alt+tabbing should not be enough.
Should I just switch to using JAVAs timer? Does this have cross-platform implications?
Thanks,
Darkflame
a) Yes, the documentation of TimerThread, which handles Timer, says:
Manages the single timer thread. Stops thread on libgdx application
pause and dispose, starts thread on resume.
b) Since libGDX Timer is nothing special than just a Thread, which listen for application change (pause, resume etc.), with list of tasks, it should be fine to use Java's Timer. Since it is from 1.3 (and the libGDX target is 1.6) it should not have any cross-platform implications.

Auto dismiss Login page when logged

I have the following page navigation in my app:
AnyPage -> Login -> Register
When the user gets registered he is automatically logged to. So I want the Login page to be closed automatically if the user go back to it and is logged.
I tried to add some code to the LoginPage.onNavigatedTo method but it doesn't work.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.navigationHelper.OnNavigatedTo(e);
if(AccountController.isLogged()){
Frame.GoBack();
}
}
How can I do it?
The OnNavigatedTo and OnNavigatedFrom methods are part of the ongoing navigation operation. When you try to start a new navigation while there is one in progress, navigation methods will stop and return false.
Simple trick is: Make your OnNavigatedTo async and add a delay before navigating back.
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
this.navigationHelper.OnNavigatedTo(e);
if(AccountController.isLogged()){
await Task.Delay(1);
Frame.GoBack();
}
}
The reason this works is: Navigation is handled on the UI Thread. As the Event Methods are void returning, they are not awaited and if you return a task inside of them, the current caller finishes his current task. Whatever comes after the await is queued to the Dispatcher and finished once he has time for it (which is immediately after the navigation operation finishes).

Java GUI - SwingWorker and gif

Ok so I am doing some taks of parsing data from document in SwingWorker which takes quite good amount of time.
And I am showing "loading gif" as JButton icon while doing this and also updating label every 15% done.
Its working ,but only problem is that gif is moving slowly or sometiems stop for sec and then start again. GIF is basically not animating smooth.
JButton is disabled and I set this gift as icon for disabled JButton.
In MainGUI class:
jButton3.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/images/loading.gif")));
After user click on button I call this:
runLoader=new CashLoader(jButton3, jLabel2, ucty, userLock);
jButton3.setEnabled(false);
jLabel2.setForeground(Color.GRAY);
executor.execute(runLoader);
CashLoader thread do this:
#Override
protected Object doInBackground() {
// long code is here
// somewhere in the code I call evey 15% done this:
publish(new PublishingClass(sum, true));
}
#Override
protected void process(List<PublishingClass> sum){
for (PublishingClass publishingThing : sum) {
if (publishingThing .isOrangeColor()) lblSum.setForeground(new Color(226,182,3));
lblSum.setText(""+publishingThing .getNumber()+" %");
}
}
#Override
protected void done(){
btnReload.setEnabled(true);
...
}
Thats sorter version of code. As you can see I am not doing anything in SwingWorker doInBackground() method with JButton. Once its set to disabled it should show gif as its icon. Which is obviously doing ,but not smooth ,it just stops for few seconds or once I move with mouse over window then it starts moving gif . It basicly shows laggy gif.

removing mouse events/controls from swing components with glasspane

I have a client-server application and i am using swing in the client side. My swing client has one main window (jframe) and lots of panels, toolbars and menubar in it.
I want to remove all client action/mouse events (or simply grab and do nothing) while client is waiting response from server by means of glasssPane.
Here is the code i wrote:
private final static MouseAdapter mouseAdapter = new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
System.out.println("MouseClicked..!");
}
};
private static Cursor WAIT_CURSOR = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
private static Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
and
public static void startWaitCursor(JComponent comp)
{
MainWindow root = ((MainWindow) comp.getTopLevelAncestor());
root.getGlassPane().setCursor(WAIT_CURSOR);
root.getGlassPane().addMouseListener(mouseAdapter);
root.getGlassPane().setVisible(true);
}
public static void stopWaitCursor(JComponent comp)
{
MainWindow root = ((MainWindow) comp.getTopLevelAncestor());
root.getGlassPane().setCursor(DEFAULT_CURSOR);
root.getGlassPane().setVisible(false);
}
but i am not able to manage the grab mouse events. Changing cursors at the glassPane is working fine but either i am not able to add mouseAdapter or am not able to make glasssPane become to the top level component.
Any idea?
Thanks.
I realized that my code is working but my problem is threading related. My code was something like:
startWaitCursor();
work(); // server request that takes time
stopWaitCursor();
and changed it to:
startWaitCursor();
SwingUtilities.invokeLater(new Runnable() {
poblic void run() {
try
{
work(); // server request
}
finally
{
stopWaitCursor();
}
by doing this modification i could see the settings i made in the startWaitCursor() method while client is waiting response from the server.
But stil there is a small problem. In startWaitCursor() method i desabled key, mouse and focus events for the glass pane but events are still captured by main frame even glassPane is displayed.
addMouseListener(new MouseAdapter() {});
addMouseMotionListener(new MouseMotionAdapter() {});
addKeyListener(this);
setFocusTraversalKeysEnabled(false);
After server response reached to client and stopWaitCursor() method is invoked the events handled in the main frame.
If i disable the main frame of my application while client is waiting than cursor is not being changed to wait_cursor, if i am not disable the main frame then cursor is being changed but the events are queued.
cheers...
After digging swing threads issues couple of days, i finally found the real answer: SwingWorker
Now my final code is something like,
startWaitCursor();
SwingWorker worker = new SwingWorker() {
public Object doInBackground()
{
doWork(); // time consuming server request
return null;
}
public void done()
{
stopWaitCursor();
}
};
worker.execute();
In startWaitCursor() method i set the glasspane visible (with alpha valued background), display a message to warn the user time consuming job is doing, set the cursor to wait_cursor (hourglass) and consume all the key, mouse events. That is it.
And by using SwingWorker my client is actually responsive (it is working as if no server request is made) but since i display the glasspane and consume all key and mouse events it feels like irresponsive.
What a relief.. SwingWorker rocks...
cheers..