Regarding your implementation, two loops need to be done in separate stages. There is no point in having two loops, like in your code, because density, you would be working with, will eventually end being density of previous frame, and SPH integration will stop being valid. You need to separate density loop and velocity integration into two separate simulation stages.
I also did not see boundary handling in your code. You can’t just rely on particle collisions alone here, as it would cause particles to stick to boundaries and loose tremendous amounts of energy. Boundary needs to be handled during velocity solve.
Mind, that you need substepping. At typical game framerates you would need a from several to several dozen substeps to have sufficient stability, unless you implement pressure clamping and significant dampening, which reduces realism in favor of performance.
Concerning visualization, for screen space fluid you would need at least custom Niagara interface with capability to draw particles to render target.
Rasterizing them manually in Niagara is very limited in functionality and particle screen size/ distance relation.
Alternatively, instead of using screen space fluid, vowelize density and raymarch through in simulation stage at lower res. Then upsample in material pass.
Regarding mobile, you can implement that all in custom compute shaders without niagara.
Last but not least, expecting realtime SPH on mobiles is gross over-expectation.